# wiki

name: ServerResponseCode
author: mnehme
ipnr: 178.135.74.150
text:
  Below are the Server Response codes which can be returned:

  200: Ok (no error)[[BR]]
  201: Ok but no rows affected. (Not always returned. Sometimes 200 will be returned instead.)[[BR]]

  Force browser actions:[[BR]]
  101: Force refresh[[BR]]

  Misc error:[[BR]]
  500: Misc error[[BR]]

  Errors in the ajax call, thrown by ajaxReq:[[BR]]
  601: function not defined[[BR]]
  602: action contains invalid chars[[BR]]
  603: missing action[[BR]]
  604: object name has invalid characters[[BR]]

  SQL:[[BR]]
  620: SQL Error[[BR]]
  621: SQL Duplicate index value[[BR]]

  Auth:[[BR]]
  630: Auth failed[[BR]]
  631: New token sent[[BR]]
  6311: Invite or reset email sent[[BR]]
  6312: Password reset[[BR]]
  632: Send email invite or change/reset password failed b/c user not registered[[BR]]
  633: User does not have access rights to this group, or this type of access right does not exist.[[BR]]
  634: Facebook auth error[[BR]]
  635: Registration failed: auto-registration is not authorized.[[BR]]
  636: User is already registered.[[BR]]
  637: User is not yet registered.[[BR]]

  Printing errors:[[BR]]
  640: Print of webpage or scripts or etc  failed[[BR]]
  641: Print of webpage or scripts or etc failed: user not defined[[BR]]
  642: Null index of input encountered when turning dialogue box into table.[[BR]]

  Thrown errors:[[BR]]
  610: Unspecified error[[BR]]
  611: E_USER_ERROR[[BR]]
  612: E_USER_WARNING[[BR]]
  No error code: E_USER_NOTICE, E_NOTICE[[BR]]

  Customer errors:[[BR]]
  700: Unspecified customer error
  701: Error in custom php functions by customer


name: FAQ
author: mnehme
ipnr: 178.135.74.150
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why his works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other. Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


name: ScriptingHacks
author: mnehme
ipnr: 178.135.74.150
text:
  == Warning ==
  Features described here are *advanced*, *experimental* and *unsupported*. Some will make it to subsequent versions, and some will be dropped.

  == Script-Defined Parameters ==

  === Startup ===
  ''These start-up features will '''ONLY WORK''' prior to start-up of the app. Therefore they must be set within a user-defined function called currentView._render() in view views_0000_index. Otherwise they will be ignored.''

  '''localStorage.sendPendingCycle''': Sets the interval at which all pending data is sent to the server (in milliseconds). When not set, this happens every minute.

  '''localStorage.reloadCycle''': Sets the interval at which the entire app auto-refreshes (in milliseconds). When not set, auto-refresh happens every 2 hours. A negative value will de-activate the reloading.

  '''localStorage.reloadMsg''': Sets the warning message that appears 30 seconds before the app auto-refreshes. When not set, the message is 'Warning: refreshing in less than 30 seconds. Please save your work!'

  == CDE Hacks ==
  The rest of this page addresses tips for your app. This section, however, gives tips for modifying/enhancing the behavior of CDE itself. All code must be executed in the chrome console.
   * Make it faster to go from Design view to Dynamics view and vice-versa: set localStorage.doNotRefresh = 1;
  Warning: this speed comes at the expense of accuracy. It is only useful if you have MANY elements in a view, making it slow
   * Search all your functions: go to http://creator.ubiqitus.com/cde/utilities/search.php?user=[your username]&searchStr=searchstring&TITLE=[app title] (Replace parameters as needed. Note that punctuation and special characters will be ignored.)
   * Test replace a string: this allows you to try out what happens if you replace all occurences of a string with another. It is zero risk, since it only affects the generated version of your app. To revert the change, all you need to do is generate again. To do this, go to http://creator.ubiqitus.com/cde/utilities/search.php?user=[your username]&searchStr=searchstring&TITLE=[app title]&replaceStr=whatever (Replace parameters as needed. Note that punctuation and special characters will be ignored.)


name: Troubleshooting
author: mnehme
ipnr: 178.135.74.150
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example, if an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.


name: Troubleshooting
author: mnehme
ipnr: 178.135.74.150
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: ScriptingHacks
author: mnehme
ipnr: 89.17.101.252
text:
  == Warning ==
  Features described here are *advanced*, *experimental* and *unsupported*. Some will make it to subsequent versions, and some will be dropped.

  == Script-Defined Parameters ==

  === Startup ===
  ''These start-up features will '''ONLY WORK''' prior to start-up of the app. Therefore they must be set within a user-defined function called currentView._render() in view views_0000_index. Otherwise they will be ignored.''

  '''localStorage.sendPendingCycle''': Sets the interval at which all pending data is sent to the server (in milliseconds). When not set, this happens every minute.

  '''localStorage.reloadCycle''': Sets the interval at which the entire app auto-refreshes (in milliseconds). When not set, auto-refresh happens every 2 hours. A negative value will de-activate the reloading.

  '''localStorage.reloadMsg''': Sets the warning message that appears 30 seconds before the app auto-refreshes. When not set, the message is 'Warning: refreshing in less than 30 seconds. Please save your work!'

  == CDE Hacks ==
  The rest of this page addresses tips for your app. This section, however, gives tips for modifying/enhancing the behavior of CDE itself. All code must be executed in the chrome console.
   * Make it faster to go from Design view to Dynamics view and vice-versa: set localStorage.doNotRefresh = 1;
  Warning: this speed comes at the expense of accuracy. It is only useful if you have MANY elements in a view, making it slow
   * Search all your functions: go to http://creator.ubiqitus.com/cde/utilities/search.php?user=[your username]&searchStr=searchstring&TITLE=[app title] (Replace parameters as needed. Note that punctuation and special characters will be ignored.)
   * Test replace a string: this allows you to try out what happens if you replace all occurences of a string with another. It is zero risk, since it only affects the generated version of your app. To revert the change, all you need to do is generate again. To do this, go to http://creator.ubiqitus.com/cde/utilities/search.php?user=[your username]&searchStr=searchstring&TITLE=[app title]&replaceStr=whatever (Replace parameters as needed. Note that punctuation and special characters will be ignored.)
   * In debug mode, by default the table of events does not include those that are built-in by CDE. If you want to include them, set localStorage._debugEventList_Full=1


name: Troubleshooting
author: mnehme
ipnr: 77.42.163.220
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: Troubleshooting
author: mnehme
ipnr: 77.42.163.220
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.
  If you are using the Facebook features, note that even though field names with the names of Facebook data fields are allowed, they can cause interference with the Facebook data collection process.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: WikiStart
author: mnehme
ipnr: 77.42.163.220
text:
  == CDE speeds up App development by 20-30x ==
  Cedrus Development Environment is a platform to allow easy and fast development of mobile and web Apps. "Fast" means 20-30x faster than normal development.
  Though primarily targeting developers, CDE can also be used by product managers to speedily create prototypes with zero-coding. These prototypes can be used as both specs and first release, and speed up the production process by breaking down coding into simple tasks.

  == How it works ==
   * '''Fast design process:''' design is done via simple drag-and-drop.
   * '''Fast coding:''' CDE makes it easy and natural to break down code into small self-contained components, reducing complexity, while making coding very fast. 
   * '''Cloud-enabled:''' Automatically creates server-side databases, automatically creates access functions for you to use which even work offline, and provide intelligent syncing abilities.
   * '''Multi-platform:''' CDE creates apps written in Ajax/HTML5 for their front-ends, and PHP for their back-ends. As a result, it can be used with Phonegap to immediately port the App to all platforms.
   * '''Iterative prototyping:''' Naturally encourages iterative development and MVP, because iterating is blindingly fast.
   * '''Infinitely extensible:''' CDE of course includes the ability to actually code, so anything that can be done by coding can be done with CDE.

  == Limitations ==
  CDE works for most Apps, except for media-rich gaming Apps. For the latter, you might want to consider using a platform such as Unity3D.

  == Starting Points ==
   * [wiki:GettingStarted Getting Started] -- A quick introduction
   * [wiki:FAQ Cedrus Development Environment FAQ] -- Frequently Asked Questions
   * [wiki:NewApp I. Creating and Configuring a New App] -- Creating and Configuring a New App
   * [wiki:NewApp II. Menus] -- Interface and Menus
   * [wiki:Advanced IV. Advanced Features] -- Advanced Features
   * [wiki:ScriptingHacks Hacks] -- Scripting hacks, allowing access to some special features.
   * [wiki:Tips Tips and Best Practices] -- Tips and Best Practices
   * [wiki:Troubleshooting Troubleshooting] -- Troubleshooting
   * [wiki:TracStartingPage Bug-reporting assistance] -- You can report bugs and request features using Trac. This links to the Trac documentation.


name: WikiStart
author: mnehme
ipnr: 77.42.163.220
text:
  == CDE speeds up App development by 20-30x ==
  Cedrus Development Environment is a platform to allow easy and fast development of mobile and web Apps. "Fast" means 20-30x faster than normal development.
  Though primarily targeting developers, CDE can also be used by product managers to speedily create prototypes with zero-coding. These prototypes can be used as both specs and first release, and speed up the production process by breaking down coding into simple tasks.

  == How it works ==
   * '''Fast design process:''' design is done via simple drag-and-drop.
   * '''Fast coding:''' CDE makes it easy and natural to break down code into small self-contained components, reducing complexity, while making coding very fast. 
   * '''Cloud-enabled:''' Automatically creates server-side databases, automatically creates access functions for you to use which even work offline, and provide intelligent syncing abilities.
   * '''Multi-platform:''' CDE creates apps written in Ajax/HTML5 for their front-ends, and PHP for their back-ends. As a result, it can be used with Phonegap to immediately port the App to all platforms.
   * '''Iterative prototyping:''' Naturally encourages iterative development and MVP, because iterating is blindingly fast.
   * '''Infinitely extensible:''' CDE of course includes the ability to actually code, so anything that can be done by coding can be done with CDE.

  == Limitations ==
  CDE works for most Apps, except for media-rich gaming Apps. For the latter, you might want to consider using a platform such as Unity3D.

  == Starting Points ==
   * [wiki:GettingStarted Getting Started] -- A quick introduction
   * [wiki:FAQ Cedrus Development Environment FAQ] -- Frequently Asked Questions
   * [wiki:NewApp I. Creating and Configuring a New App] -- Creating and Configuring a New App
   * [wiki:Menus II. Menus] -- Interface and Menus
   * [wiki:Advanced IV. Advanced Features] -- Advanced Features
   * [wiki:ScriptingHacks Hacks] -- Scripting hacks, allowing access to some special features.
   * [wiki:Tips Tips and Best Practices] -- Tips and Best Practices
   * [wiki:Troubleshooting Troubleshooting] -- Troubleshooting
   * [wiki:TracStartingPage Bug-reporting assistance] -- You can report bugs and request features using Trac. This links to the Trac documentation.


name: Menus
author: mnehme
ipnr: 77.42.163.220
text:
  == Design View ==

  === Main Menu ===

  ==== File Submenu ====
  From left to right:

   * ''Revert 15 sec'': If you need to undo, this cancels anything you did in the last 15 or so seconds.
   * ''Delete Page'': Deletes the current page.
   * ''Rename Page'': Renames the current page.
   * ''Copy Page'': Copies the current page (without its scripts).
   * ''Move or Copy Element'': Moves or copies the currently selected element to another page.

  ==== Text Submenu ====
  Only visible when an element is selected.

  From left to right:

   * ''Text'': Edit the text, and format it.
   * ''Italics, bold, underline, strikethrough'': Make the text italics, bold, underline, or strikethrough.
   * ''Horizontal Alignment buttons'': Align the text left, right, justify or center
   * ''Vertical Alignment buttons'': Align text top, center or bottom.

  ==== Format Button ====
  Only visible when an element is selected.

  Allows formatting of the element.
  ALSO ALLOWS SETTING ITS CSS CLASS.

  ==== Box Submenu ====
  Only visible when an element is selected.

  Round or un-round the corners of the element.

  ==== Position Submenu ====
  Only visible when an element is selected.

  From left to right:

   * ''Align and Center Sub-elements'': These serve to align and center the sub-elements of the selected element. As a result, they are only useful if you have selected a group. (To group several elements, click on them rapidly one after the other. The group appears as a light beige rectangle. You can select it by clicking on it.)
   * ''Move to front/back'': These are "move to front" and "move to back." If the selected element is part of a group, the reordering is done relatively to other elements of the group and not relative to the entire page. To reorder relative to the entire page, do the same to the group. Technical note: this works via the z-index of the element.
   * ''Distribute'': These serve to distribute the sub-elements of the selected element. As a result, they are only useful if you have selected a group. (To group several elements, click on them rapidly one after the other. The group appears as a light beige rectangle. You can select it by clicking on it.)
   * ''Add or remove Gridlines'': These add/remove horizontal and vertical gridlines. The gridlines are added in the middle, and can be dragged to wherever you want them. The button with an "x" removes all gridlines.

  ==== "Shapes, Images and Buttons" Submenu ====
  Only visible when an element is selected.

  From left to right:

   * ''Image'': Add an image.
   * ''Shape'': Add a user-defined shape.
   * ''User Input Field'': Add a user input field.
   * ''Resource'': Add a page as though it were an element. This allows you to reuse design elements. For example, if you want to reuse a specific design, you can create it in its own page, and then insert it on other pages wherever you need it via this button.


name: Advanced
author: mnehme
ipnr: 178.135.222.200
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, go to “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.'''

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  For more about Facebook permissions see http://developers.facebook.com/docs/authentication/permissions/

  '''fbuserdata Table'''[[BR]]
  Fields: fbid, firstname, lastname, email, location, gender, locale, locale, birthday funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API, with the exception of:[[BR]]
  fbid: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =
  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'jane';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: Troubleshooting
author: mnehme
ipnr: 178.135.222.200
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.
  If you are using the Facebook features, note that even though field names with the names of Facebook data fields are allowed, they can cause interference with the Facebook data collection process. This also applies to field names ad_id and ad.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: Troubleshooting
author: mnehme
ipnr: 178.135.222.200
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  '''Facebook data is not being collected.'''
  Did you register your app on Facebook? Did you obtain the user's permission? Did you create tables called fbuserdata and fbusergraph? These are the most common issues.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.
  If you are using the Facebook features, note that even though field names with the names of Facebook data fields are allowed, they can cause interference with the Facebook data collection process. This also applies to field names ad_id and ad.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: Troubleshooting
author: mnehme
ipnr: 178.135.222.200
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  '''Facebook data is not being collected.'''

  Did you register your app on Facebook? Did you obtain the user's permission? Did you create tables called fbuserdata and fbusergraph? These are the most common issues.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.
  If you are using the Facebook features, note that even though field names with the names of Facebook data fields are allowed, they can cause interference with the Facebook data collection process. This also applies to field names ad_id and ad.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: Advanced
author: mnehme
ipnr: 178.135.222.200
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, go to “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.'''

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  For more about Facebook permissions see http://developers.facebook.com/docs/authentication/permissions/

  '''fbuserdata Table'''[[BR]]
  Fields: fbid, firstname, lastname, email, location, gender, locale, locale, birthday funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API, with the exception of:[[BR]]
  fbid: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =
  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'jane';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: Advanced
author: mnehme
ipnr: 178.135.222.200
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, go to “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.'''

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  For more about Facebook permissions see http://developers.facebook.com/docs/authentication/permissions/

  '''fbuserdata Table'''[[BR]]
  Fields: fbid, firstname, lastname, email, location, gender, locale, locale, birthday funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API, with the exception of:[[BR]]
  fbid: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =
  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: Advanced
author: mnehme
ipnr: 178.135.222.200
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, go to “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.'''

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  For more about Facebook permissions see http://developers.facebook.com/docs/authentication/permissions/

  '''fbuserdata Table'''[[BR]]
  Fields: id, name, firstname, lastname, email, location, gender, locale, birthday, funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API (with the underscores removed), with the exception of:[[BR]]
  id: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =
  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      alert(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      alert(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      alert('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    alert("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: Advanced
author: mnehme
ipnr: 178.135.161.211
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, go to “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.'''

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  For more about Facebook permissions see http://developers.facebook.com/docs/authentication/permissions/

  '''fbuserdata Table'''[[BR]]
  Fields: id, name, firstname, lastname, email, location, gender, locale, birthday, funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API (with the underscores removed), with the exception of:[[BR]]
  id: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =
  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)
  - Have a browser that equipped with a console, accessible through the javascript console object. The output will be printed to console.

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: Troubleshooting
author: mnehme
ipnr: 178.135.161.211
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  '''Facebook data is not being collected.'''

  Did you register your app on Facebook? Did you obtain the user's permission? Did you create tables called fbuserdata and fbusergraph? These are the most common issues.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.
  If you are using the Facebook features, note that even though field names with the names of Facebook data fields are allowed, they can cause interference with the Facebook data collection process. This also applies to field names ad_id and ad.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  '''Are you using alert()?'''

  Because it interrupts the flow of the program, the alert() function can cause some subtle issues, very difficult to debug. Also, if there are too many simultaneous alerts firing, it can crash the browser.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: Troubleshooting
author: mnehme
ipnr: 178.135.161.211
text:
  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Close the app
   * Clear the browser history (usually with ctrl-shift-del)
   * Re-open the app
   * Force refresh (ctrl-F5)

  = Common Issues =
  '''When I open the app I'm creating, I have an unexpected request to login! '''

  Unless you disabled authentication from the main panel, this is normal. The reason you didn't get a login request earlier is because, if you are logged into CDE, it automatically makes you be logged into any apps you are working on. In order to fix this issue, either log back into CDE, or log into the app.

  '''The script edits I made are not being taken into account.'''

  Are you sure you've regenerated? If so, then the issue is probably Chrome browser refusing to update the js file. Force an update by opening the js file from the address bar.

  '''The server is sending back an empty data set, when I expect otherwise.'''

  This is generally a user access rights issue. By default a given user does not have access to data recorded in the name of any other user.

  If you have requested advanced user access rights from CDE, then the problem is still probably due to an access rights violation. The following code shows you an example of how to set about checking access rights:
  {{{
  #!java

  /*
  The following code checks whether a user has certain access rights. This only makes sense if you have:
  - Contacted CDE to request that advanced user access rights be enabled. (It is not enabled by default for security reasons.)
  - Enabled authentication for the app (from CDE's main panel)

  For this code to work, you will need to:
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)
  - Replace JaneClark, SalesDept and admin in the below with the name of the user whose rights you want to check, the name of the account to
   access, and the name (if relevant) of the group through which you want to access the account
  - Have a browser that equipped with a console, accessible through the javascript console object. The output will be printed to console.

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var user = 'JaneClark'; // Name of the user whose access rights to check. Change as needed
  var accountToAccess = 'SalesDept'; // Name of the account which the user would like to access. Change as needed
  var group='admin';
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      logg('List of users: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all accounts to which the user has access via _altUser
  // Read
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = user;
  cfg.data['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg('List of accounts to which your user has read access via _altUser: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);
  // Write
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = user;
  cfg.data['type'] = 'write';
  cfg.successFunc = function(list, allData){
      logg('List of accounts to which your user has write access via _altUser: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);
  // Read-write
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = user;
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg('List of accounts to which your user has read-write access via _altUser: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if user has read-write access to the account accountToAccess using _altUser
  // Read
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = user;
  cfg.data['checkedGroup'] = accountToAccess;
  cfg.data['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg('Does your user have read access to the account using _altUser: '+ dump(allData), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkPermissions'].reload(cfg);
  // Write
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = user;
  cfg.data['checkedGroup'] = accountToAccess;
  cfg.data['type'] = 'write';
  cfg.successFunc = function(list, allData){
      logg('Does your user have write access to the account using _altUser: '+ dump(allData), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkPermissions'].reload(cfg);
  // Read-write
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = user;
  cfg.data['checkedGroup'] = accountToAccess;
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg('Does your user have read-write access to the accountToAccess using _altUser: '+ dump(allData), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if the user has read/write access to the accountToAccess via the  _group called group
  // Read
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = user;
  cfg.params['checkedGroup'] = group;
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg('List of accounts to which your user has read access via _group: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);
  // Write
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = user;
  cfg.params['checkedGroup'] = group;
  cfg.params['type'] = 'write';
  cfg.successFunc = function(list, allData){
  logg('List of accounts to which your user has write access via _group: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);
  // Read-write
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = user;
  cfg.params['checkedGroup'] = group;
  cfg.params['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg('List of accounts to which your user has read-write access via _group: '+ dump(list), 'warn');
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg, 'warn');
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  }}}



  '''Facebook data is not being collected.'''

  Did you register your app on Facebook? Did you obtain the user's permission? Did you create tables called fbuserdata and fbusergraph? These are the most common issues.

  = Troubleshooting Questions =
  '''Did you use _ (or any other special character such as -, +, etc) in any variable names? Or page or dialogue name? Or script name? Or database field name?'''

  "_" is a reserved character. Using it is likely to cause malfunctions.

  '''Are you using a field name called by one of the reserved field names?'''

  The reserved field names are: 'ajaxObj', 'user', 'date', 'datenow', 'signed_request' as well as any field containing a reserved character such as "_". Note also that 'index', though user-accessible (not reserved), has the special purpose of being the index column of a table. Using it for any other purpose will yield unpredictable results.
  If you are using the Facebook features, note that even though field names with the names of Facebook data fields are allowed, they can cause interference with the Facebook data collection process. This also applies to field names ad_id and ad.

  '''Did you use two tables with the same name except for caps? Or two apps with the same name once stripped of spaces and reduced to lower case?'''

  Due to a bug in mysql windows version, table names and database names are not case sensitive.

  '''Are you using alert()?'''

  Because it interrupts the flow of the program, the alert() function can cause some subtle issues, very difficult to debug. Also, if there are too many simultaneous alerts firing, it can crash the browser.

  = Unusual issues, grouped by symptom =
  == removeEvent not working ==
  This is typically due to a change in variable reference. For example:
   * Example 1: If an event listener is added to currentView.events, and the view changes, then trying to remove the event from currentView.events will not work, since currentView now points to the new view.
   * Example 2: If the event listener triggers function currentView.myFunc, and the view changes, then trying to remove the event will not work, since currentView.myFunc no longer points to the same function.


name: WikiStart
author: mnehme
ipnr: 94.187.52.131
text:
  == CDE speeds up App development by 20-30x ==
  Cedrus Development Environment is a platform to allow easy and fast development of mobile and web Apps. "Fast" means 20-30x faster than normal development.
  Though primarily targeting developers, CDE can also be used by product managers to speedily create prototypes with zero-coding. These prototypes can be used as both specs and first release, and speed up the production process by breaking down coding into simple tasks.

  == How it works ==
   * '''Fast design process:''' design is done via simple drag-and-drop.
   * '''Fast coding:''' CDE makes it easy and natural to break down code into small self-contained components, reducing complexity, while making coding very fast. 
   * '''Cloud-enabled:''' Automatically creates server-side databases, automatically creates access functions for you to use which even work offline, and provide intelligent syncing abilities.
   * '''Multi-platform:''' CDE creates apps written in Ajax/HTML5 for their front-ends, and PHP for their back-ends. As a result, it can be used with Phonegap to immediately port the App to all platforms.
   * '''Iterative prototyping:''' Naturally encourages iterative development and MVP, because iterating is blindingly fast.
   * '''Infinitely extensible:''' CDE of course includes the ability to actually code, so anything that can be done by coding can be done with CDE.

  == Limitations ==
  CDE works for most Apps, except for media-rich gaming Apps. For the latter, you might want to consider using a platform such as Unity3D.

  == Status ==
  CDE is in private beta. To request an invitation, send an email to webmaster at ubiqitus dot com.

  == Starting Points ==
   * [wiki:GettingStarted Getting Started] -- A quick introduction
   * [wiki:FAQ Cedrus Development Environment FAQ] -- Frequently Asked Questions
   * [wiki:NewApp I. Creating and Configuring a New App] -- Creating and Configuring a New App
   * [wiki:Menus II. Menus] -- Interface and Menus
   * [wiki:Advanced IV. Advanced Features] -- Advanced Features
   * [wiki:ScriptingHacks Hacks] -- Scripting hacks, allowing access to some special features.
   * [wiki:Tips Tips and Best Practices] -- Tips and Best Practices
   * [wiki:Troubleshooting Troubleshooting] -- Troubleshooting
   * [wiki:TracStartingPage Bug-reporting assistance] -- You can report bugs and request features using Trac. This links to the Trac documentation.


name: WikiStart
author: mnehme
ipnr: 94.187.52.131
text:
  == CDE speeds up App development by 20-30x ==
  Cedrus Development Environment is a platform to allow easy and fast development of mobile and web Apps. "Fast" means 20-30x faster than normal development.
  Though primarily targeting developers, CDE can also be used by product managers to speedily create prototypes with zero-coding. These prototypes can be used as both specs and first release, and speed up the production process by breaking down coding into simple tasks.

  == How it works ==
   * '''Fast design process:''' design is done via simple drag-and-drop.
   * '''Fast coding:''' CDE makes it easy and natural to break down code into small self-contained components, reducing complexity, while making coding very fast. 
   * '''Cloud-enabled:''' Automatically creates server-side databases, automatically creates access functions for you to use which even work offline, and provide intelligent syncing abilities.
   * '''Multi-platform:''' CDE creates apps written in Ajax/HTML5 for their front-ends, and PHP for their back-ends. As a result, it can be used with Phonegap to immediately port the App to all platforms.
   * '''Iterative prototyping:''' Naturally encourages iterative development and MVP, because iterating is blindingly fast.
   * '''Infinitely extensible:''' CDE of course includes the ability to actually code, so anything that can be done by coding can be done with CDE.

  == Limitations ==
  CDE works for most Apps, except for media-rich gaming Apps. For the latter, you might want to consider using a platform such as Unity3D.

  == Status ==
  CDE is in private beta. To request an invitation, send an email to webmaster at ubiqitus dot com.

  == Starting Points ==
   * [wiki:GettingStarted Getting Started] -- A quick introduction
   * [wiki:FAQ Cedrus Development Environment FAQ] -- Frequently Asked Questions
   * [wiki:NewApp I. Creating and Configuring a New App] -- Creating and Configuring a New App
   * [wiki:Menus II. Interface and Menus] -- Interface and Menus
   * [wiki:Scripting III. Scripting] -- Scripting
   * [wiki:Advanced IV. Advanced Features] -- Advanced Features
   * [wiki:ScriptingHacks Hacks] -- Scripting hacks, allowing access to some special features.
   * [wiki:Tips Tips and Best Practices] -- Tips and Best Practices
   * [wiki:Troubleshooting Troubleshooting] -- Troubleshooting
   * [wiki:TracStartingPage Bug-reporting assistance] -- You can report bugs and request features using Trac. This links to the Trac documentation.


name: Scripting
author: mnehme
ipnr: 94.187.52.131
text:
  = Overview =
  All you need in order to be able to script in CDE is knowledge of javascript, which is the language used. Also, you will find the pointers at [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Scripting FAQ: Scripting] to be helpful. As for the full scripting reference, it will be provided from within CDE. This section is just an overview.

  = Events =
  More information about events in CDE can be found at the following articles:
   * [CustomEvents Custom Events Reference]: list of events available in CDE, in addition to those built into javascript
   * [AddEvent addEvent] and [RemoveEvent removeEvent]: functions used to add and remove event listeners, respectively. 

  = Moving between views =
  See [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Scripting FAQ: Moving Between Views]

  = Sending Data to and From the Server =
  See [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprogramaticallyaccesstheclouddata FAQ: Accessing the Cloud]


name: FAQ
author: mnehme
ipnr: 94.187.52.131
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why his works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other. Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== What sql operations other than 'select' are available? ====
  Sql operations available by default for every cloud object are:
   * '''select''': an sql select
   * '''update''': updates a record, or inserts it if it does not already exist.
   * '''strictupdate''': an sql update. If the record with the given index does not exist, then does nothing (except print a warning to the console)
   * '''insert''': an sql insert
   * '''selectdistinct''': list all of the distinct values taken by a given column. Is equivalent to an sql "select distinct" applied to one column
   * '''delete''': deletes the record with the given index. This is not a definitive delete: the record can be undeleted.
   * '''undelete''': undeletes the record with the given index
   * '''deleteFinal''': definitively deletes a record. Corresponds to sql delete.


  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


name: Advanced
author: mnehme
ipnr: 178.135.218.250
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, you will need to:
  1. '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.''' This is done by setting localStorage.FBScope, for example:
  {{{
  #!java
  localStorage.FBScope='email,user_birthday';// Will request email and birthday as additional permissions
  }}}
  For more about Facebook permissions, including the list of all possible permissions see http://developers.facebook.com/docs/authentication/permissions/
  2. Go to the “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) 

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  '''fbuserdata Table'''[[BR]]
  Fields: id, name, firstname, lastname, email, location, gender, locale, birthday, funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API (with the underscores removed), with the exception of:[[BR]]
  id: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =
  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)
  - Have a browser that equipped with a console, accessible through the javascript console object. The output will be printed to console.

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: WikiStart
author: mnehme
ipnr: 178.135.218.250
text:
  == CDE speeds up App development by 20-30x ==
  Cedrus Development Environment is a platform to allow easy and fast development of mobile and web Apps. "Fast" means 20-30x faster than normal development.
  Though primarily targeting developers, CDE can also be used by product managers to speedily create prototypes with zero-coding. These prototypes can be used as both specs and first release, and speed up the production process by breaking down coding into simple tasks.

  == How it works ==
   * '''Fast design process:''' design is done via simple drag-and-drop.
   * '''Fast coding:''' CDE makes it easy and natural to break down code into small self-contained components, reducing complexity, while making coding very fast. 
   * '''Cloud-enabled:''' Automatically creates server-side databases, automatically creates access functions for you to use which even work offline, and provide intelligent syncing abilities.
   * '''Multi-platform:''' CDE creates apps written in Ajax/HTML5 for their front-ends, and PHP for their back-ends. As a result, it can be used with Phonegap to immediately port the App to all platforms.
   * '''Iterative prototyping:''' Naturally encourages iterative development and MVP, because iterating is blindingly fast.
   * '''Infinitely extensible:''' CDE of course includes the ability to actually code, so anything that can be done by coding can be done with CDE.
   * '''Built-in security:''' The basic security precautions (e.g. against injection attacks) are built-in, so you can focus on developing your app instead of spending time on security.

  == Limitations ==
  CDE works for most Apps, except for media-rich gaming Apps. For the latter, you might want to consider using a platform such as Unity3D.

  == Status ==
  CDE is in private beta. To request an invitation, send an email to webmaster at ubiqitus dot com.

  == Starting Points ==
   * [wiki:GettingStarted Getting Started] -- A quick introduction
   * [wiki:FAQ Cedrus Development Environment FAQ] -- Frequently Asked Questions
   * [wiki:NewApp I. Creating and Configuring a New App] -- Creating and Configuring a New App
   * [wiki:Menus II. Interface and Menus] -- Interface and Menus
   * [wiki:Scripting III. Scripting] -- Scripting
   * [wiki:Advanced IV. Advanced Features] -- Advanced Features
   * [wiki:ScriptingHacks Hacks] -- Scripting hacks, allowing access to some special features.
   * [wiki:Tips Tips and Best Practices] -- Tips and Best Practices
   * [wiki:Troubleshooting Troubleshooting] -- Troubleshooting
   * [wiki:TracStartingPage Bug-reporting assistance] -- You can report bugs and request features using Trac. This links to the Trac documentation.


name: ScriptingHacks
author: mnehme
ipnr: 77.42.137.43
text:
  == Warning ==
  Features described here are *advanced*, *experimental* and *unsupported*. Some will make it to subsequent versions, and some will be dropped.

  == Script-Defined Parameters ==

  === Startup ===
  ''These start-up features will '''ONLY WORK''' prior to start-up of the app. Therefore they must be set within a user-defined function called currentView._render() in view views_0000_index. Otherwise they will be ignored.''

  '''localStorage.sendPendingCycle''': Sets the interval at which all pending data is sent to the server (in milliseconds). When not set, this happens every minute.

  '''localStorage.reloadCycle''': Sets the interval at which the entire app auto-refreshes (in milliseconds). When not set, auto-refresh happens every 2 hours. A negative value will de-activate the reloading.

  '''localStorage.reloadMsg''': Sets the warning message that appears 30 seconds before the app auto-refreshes. When not set, the message is 'Warning: refreshing in less than 30 seconds. Please save your work!'

  == CDE Hacks ==
  Whereas the other sections of this page address tips for your app, this section gives tips for modifying/enhancing the behavior of CDE itself. All code must be executed in the chrome console.
   * Make it faster to go from Design view to Dynamics view and vice-versa: set localStorage.doNotRefresh = 1;
  Warning: this speed comes at the expense of accuracy. It is only useful if you have MANY elements in a view, making it slow
   * Search all your functions: go to http://creator.ubiqitus.com/cde/utilities/search.php?user=[your username]&searchStr=searchstring&TITLE=[app title] (Replace parameters as needed. Note that punctuation and special characters will be ignored.)
   * Test replace a string: this allows you to try out what happens if you replace all occurences of a string with another. It is zero risk, since it only affects the generated version of your app. To revert the change, all you need to do is generate again. To do this, go to http://creator.ubiqitus.com/cde/utilities/search.php?user=[your username]&searchStr=searchstring&TITLE=[app title]&replaceStr=whatever (Replace parameters as needed. Note that punctuation and special characters will be ignored.)
   * In debug mode, by default the table of events does not include those that are built-in by CDE. If you want to include them, set localStorage._debugEventList_Full=1


name: Menus
author: mnehme
ipnr: 77.42.137.43
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Design View =

  == Main Menu ==

  === File Submenu ===
  From left to right:

   * ''Revert 15 sec'': If you need to undo, this cancels anything you did in the last 15 or so seconds.
   * ''Delete Page'': Deletes the current page.
   * ''Rename Page'': Renames the current page.
   * ''Copy Page'': Copies the current page (without its scripts).
   * ''Move or Copy Element'': Moves or copies the currently selected element to another page.

  === Text Submenu ===
  Only visible when an element is selected.

  From left to right:

   * ''Text'': Edit the text, and format it.
   * ''Italics, bold, underline, strikethrough'': Make the text italics, bold, underline, or strikethrough.
   * ''Horizontal Alignment buttons'': Align the text left, right, justify or center
   * ''Vertical Alignment buttons'': Align text top, center or bottom.

  === Format Button ===
  Only visible when an element is selected.

  Allows formatting of the element.
  ALSO ALLOWS SETTING ITS CSS CLASS.

  === Box Submenu ===
  Only visible when an element is selected.

  Round or un-round the corners of the element.

  === Position Submenu ===
  Only visible when an element is selected.

  From left to right:

   * ''Align and Center Sub-elements'': These serve to align and center the sub-elements of the selected element. As a result, they are only useful if you have selected a group. (To group several elements, click on them rapidly one after the other. The group appears as a light beige rectangle. You can select it by clicking on it.)
   * ''Move to front/back'': These are "move to front" and "move to back." If the selected element is part of a group, the reordering is done relatively to other elements of the group and not relative to the entire page. To reorder relative to the entire page, do the same to the group. Technical note: this works via the z-index of the element.
   * ''Distribute'': These serve to distribute the sub-elements of the selected element. As a result, they are only useful if you have selected a group. (To group several elements, click on them rapidly one after the other. The group appears as a light beige rectangle. You can select it by clicking on it.)
   * ''Add or remove Gridlines'': These add/remove horizontal and vertical gridlines. The gridlines are added in the middle, and can be dragged to wherever you want them. The button with an "x" removes all gridlines.

  === "Shapes, Images and Buttons" Submenu ===
  Only visible when an element is selected.

  From left to right:

   * ''Image'': Add an image.
   * ''Shape'': Add a user-defined shape.
   * ''User Input Field'': Add a user input field.
   * ''Resource'': Add a page as though it were an element. This allows you to reuse design elements. For example, if you want to reuse a specific design, you can create it in its own page, and then insert it on other pages wherever you need it via this button.


name: Advanced
author: mnehme
ipnr: 77.42.137.43
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, you will need to:
  1. '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.''' This is done by setting localStorage.FBScope, for example:
  {{{
  #!java
  localStorage.FBScope='email,user_birthday';// Will request email and birthday as additional permissions
  }}}
  For more about Facebook permissions, including the list of all possible permissions see http://developers.facebook.com/docs/authentication/permissions/
  2. Go to the “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) 

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  '''fbuserdata Table'''[[BR]]
  Fields: id, name, firstname, lastname, email, location, gender, locale, birthday, funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API (with the underscores removed), with the exception of:[[BR]]
  id: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =

  == Debugging ==
  '''Using object.watch and object.unwatch'''

  You can watch an object property using object.watch. For example, if you want to watch the parameter currentView.alpha, use the following code:

  {{{
  #!java
  currentView.watch('alpha',function (id, oldval, newval) {
      logg( "currentView." + id + " changed from " + oldval + " to " + newval);
      return newval; // DO NOT FORGET THIS LINE! OTHERWISE the property will be reset to undefined
  });
  }}}

  To unwatch, use:
  {{{
  #!java
  currentView.unwatch('alpha'});
  }}}

  '''Using logState'''

  If 'myVar' is an object, running {{{logState(myVar)}}} will set watches on each of myVar's properties.

  Example 1: You can watch changes to the program state, step by step, by doing the following:
  1. Whenever you come across a state parameter while coding, represent it as a property of an object called "state". For instance use state.startTime, instead of simple startTime. Since in CDE very little state is supposed to leak between views, this object should not be very large.
  2. Every time you add a new property to "state", call {{{logState(state);}}}
  3. From then on, CDE will log all changes to existing properties of state. Console output resembles:
  {{{
  16:39:170: State changed: Property "startTime" changed from 1 to 6.
  Before, state was: {"startTime":1,"b":2,"c":2} 
  }}}

  Example 2: If you would like to keep track of any changes to the state of the current view, run {{{logState(currentView);}}}

  ''Note: the watch only applies to properties already existing the last time logState was called. Also, it does not identify changes to subproperties.''

  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)
  - Have a browser that equipped with a console, accessible through the javascript console object. The output will be printed to console.

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: Advanced
author: mnehme
ipnr: 77.42.137.43
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Advanced Configuration =
  == Adding CSS ==
  A CSS file can be uploaded from the main panel, in the "Upload Files" section.

  == Modifying Default Dialogue Boxes ==
  You can redesign (overwrite) the default login dialogue box and the registration dialogue box, as follows:
   * Login dialogue box: Create a dialogue box called dialogues/login__.php
   * Registration dialogue box: Create a dialogue box called dialogues/register__.php

  Note: By default, these names are not allowed. You will need to create the dialogue box, open it from the "Design" tab, and rename it (File menu -> Rename)

  == Facebook Integration ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that!

  To automatically save user data on access to the app, you will need to:
  1. '''Make sure your app requests permission from the user to access any needed field, otherwise it will not work.''' This is done by setting localStorage.FBScope, for example:
  {{{
  #!java
  localStorage.FBScope='email,user_birthday';// Will request email and birthday as additional permissions
  }}}
  For more about Facebook permissions, including the list of all possible permissions see http://developers.facebook.com/docs/authentication/permissions/
  2. Go to the “Data” tab, and create a table called fbuserdata and another called fbusergraph with a selection of fields from the below. (These are the supported fields.) 

  User data is automatically saved into the two tables as described below PROVIDED you have obtained the relevant permissions from the user.

  '''fbuserdata Table'''[[BR]]
  Fields: id, name, firstname, lastname, email, location, gender, locale, birthday, funnelStage[[BR]]
  All of these are defined exactly as in the Facebook API (with the underscores removed), with the exception of:[[BR]]
  id: the user's facebook id[[BR]]
  funnelStage (beta feature): Keeps track of whether the user is a new user or a returning user[[BR]]

  Note that for some types of uses (e.g. app loaded in a canvas) ageMin and ageMax may be available. 

  '''fbusergraph Table''' (beta feature)[[BR]]
  Fields: name, category[[BR]]
  Each row of this table corresponds to a user like/interest/group.[[BR]]
  name: name of the like/interest/group[[BR]]
  category: a broad category of the like/interest/group

  = Advanced Programming =

  == Debugging ==
  '''Using object.watch and object.unwatch'''

  You can watch an object property using object.watch. For example, if you want to watch the parameter currentView.alpha, use the following code:

  {{{
  #!java
  currentView.watch('alpha',function (id, oldval, newval) {
      logg( "currentView." + id + " changed from " + oldval + " to " + newval);
      return newval; // DO NOT FORGET THIS LINE! OTHERWISE the property will be reset to undefined
  });
  }}}

  To unwatch, use:
  {{{
  #!java
  currentView.unwatch('alpha'});
  }}}

  '''Using logState'''

  logState allows you to observe the logical steps your app is flowing through without having to search for where exactly a specific change to a variable is occurring. It was originally introduced as a workaround for how ipad doesn’t support breakpoints; but it is useful in its own right.

  How it works: If 'myVar' is an object, running {{{logState(myVar)}}} will set watches on each of myVar's properties.

  Example 1: You can watch changes to the program state, step by step, by doing the following:
  1. Whenever you come across a state parameter while coding, represent it as a property of an object called "state". For instance use state.startTime, instead of simple startTime. Since in CDE very little state is supposed to leak between views, this object should not be very large.
  2. Every time you add a new property to "state", call {{{logState(state);}}}
  3. From then on, CDE will log all changes to existing properties of state. Console output resembles:
  {{{
  16:39:170: State changed: Property "startTime" changed from 1 to 6.
  Before, state was: {"startTime":1,"b":2,"c":2} 
  }}}

  Example 2: If you would like to keep track of any changes to the state of the current view, run {{{logState(currentView);}}}

  ''Note: the watch only applies to properties already existing the last time logState was called. Also, it does not identify changes to subproperties.''

  == Custom Functions ==
  In order to define your own custom functions and libraries, you can upload a javascript file from the main panel, in the "Upload Files" section. However it is recommended to work via the CDE platform whenever possible: CDE reduces bugs by giving functions local scope instead of global scope.

  This custom file is loaded after all of the default CDE scripts (but before execution begins). As a result, it can be used to over-write/replace CDE functionality.

  == Direct Server Access via PHP ==
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  Direct Server Access is only activated on request. It allows the user to create php functions and directly access mysql. (It does NOT give shell access!)

  Quick Tips for using Direct Server Access:
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql)
   * Sql data out, e.g. select statement, are a bit more complex. See helper functions below.
   * To send back an "ok": either return true, or: ajaxReply(200,'Ok');
   * To send back an error code: ajaxReply(633,$errstr); (Each response from the server comes with a code indicating that all is ok (200), or some issue. Click here for [ServerResponseCode Server Response Code Reference])
   * To execute an sql statement ($sql), and then send back a code: ajaxReply(200,'Ok',$sql);
   * To return an array of data $dataArray: ajaxReply('200','Ok',array("data"=>$dataArray));// To access, you will need to use the SECOND argument of the javascript success function as in:
  {{{
  cfg.successFunc = function(list, arr){ 
      logg(dump(arr));
    };
  }}}
   * Be careful about data access rights: your sql statements can affect any and all data. In order to restrict to the data available to the current user, you will typically need to add " WHERE `user` = '[insert current user here]'" to your sql clauses

  Some available helper functions exist, which handle data encapsulation and access rights issues automatically for you:
  {{{
  // Access the global variable representing the table
  global $cld_myTable; // If you have a table called myTable, then object $cld_myTable will automatically exist in global scope
  $cld = $cld_myTable;

  // The below are example helper functions. They execute sql statements. $row is an associative array of form columnName=>value
  //$cld->sqlSelectWhere($user, $where); // $where (string, optional) is a mysql where clause.
  //$cld->sqlInsert($user, $index, $row, false); // false => sql statement is not executed. (Useful if you do not want the ajax to return until further processing has been done. Omit it if you want immediate execution.)
  //$cld->sqlUpdate($user, $index, $row);
  //$cld->sqlDelete($user, $index); // This is not a permanent delete. It marks the row as deleted so that the app ignores it, but without removing it
  //$cld->sqlUnDelete($user, $index); 
  //$cld->sqlDeleteFinal($user, $index); // Permanent delete
  }}}

  Note that there are further helper functions available for those who have Advanced Rights Configuration enabled. If this is the case for you, they are available on request.


  == Login and Registration ==
  '''Warning:''' use only if you are an advanced programmer, and at your own risk. The following allows you to overwrite CDE functionality with some of your own. It is very powerful, but it also gives you the power to mess things up. This functionality is unsupported, and may break backward compatibility.

  First, you need to give yourself access to the advanced scripts:
  1. Click on the "Design" tab
  2. Create a new page with a random name such as 'temp'
  3. In the tree, click on views/temp/index.php
  4. Rename it to views/index.php (File menu -> Rename)
  5. Open this view in the "Dynamics" tab

  Once you have done this, you can overwrite any one of the following default CDE functions with one of your own:
   * currentView.setLogin(): Sets up login functionality. The default version (which you may overwrite) does the following:
     * Defines the callback function on authentication failure. This callback launches the login dialogue box (and also enables Facebook login where relevant), and attaches the relevant callbacks to buttons of this dialogue box. If registration by new users is allowed, it also adds a link to a Registration dialogue box, and adds callbacks to the buttons of that box.
     * Adds event listeners to authEvents (for events AuthFail, UserNotRegistered and UserUndefined)
     * Checks whether app was opened via a link in a reset password email, and handles this scenario
     * Executes auth();
   * currentView.loginHandle(): Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setRegistration(): Called by default when user clicks on the registration link in the Login dialogue box. (Of course, it will not be called if you overwrote currentView.setLogin())
   * currentView.registrationHandle: Called by default when user clicks on the login button. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.registrationValidation(): Function for validation of user registration dialogue box fields. (Of course, it will not be called if you overwrote currentView.setLogin()) 
   * currentView.setPeriodic: Function defining any housekeeping tasks that must be executed on a regular schedule. By default, these are:
     * Refreshing the app every 2 hours
     * Sending any pending data to the server every minute

  In order to overwrite, from the Dynamics tab go to the script view by clicking on any element of the page.

  == User Access Rights ==
  === Basic Rights Configuration ===
  By default, each user has access to his own data, and to no other person's. So generally, there is no need to do anything at all!

  === What if I need to share data among all accounts? ===
  Some special accounts have been created for allowing sharing of data. They are:
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for reading and writing data to such accounts:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


  === Advanced Rights Configuration ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  CDE's default user rights management is sufficient for most apps, so you generally need not do anything. However certain apps require advanced user management capabilities. CDE allows for this, with some powerful but complex functionality. 

  The fundamental principle is that you can use javascript to grant one account (Account A) access to the contents of another account (Account B). This access can be read, write or read-write

  Once this is done, when logged in as Account A, you will have the ability to read and/or write to account B. Depending on how you configure this access, it can: 
   * Either be restricted to the Account B itself, as though the current user (Account A) were using Account B temporarily (similar to using su or sudo in a linux system) [Alternative user scenario]
   * Or, it can give access not only to Account B but also to all accounts that Account B has the same access rights to. So, in this case, if for example Account A has read-write access to Account B, and Account B has read access to Account C, then Account A will be enabled to have read access (but not write access) to Account C. Note that this allows simulation of groups. To do this, simply create an account to act as group, and give it the relevant rights to other accounts. Thus, the group has been created. To add, for instance, Account A to the group as group member, simply give Account A the relevant read-write access to the group account. [Groups scenario]

  Below is some code illustrating how all this is done.

  In addition, there exist some general-purpose accounts which have pre-defined rights:
   * admin: has read-write access to all accounts
   * _public_r: all accounts can read data written into this account
   * _public_w: all accounts can write (but not read) data into this account
   * _public_rw: all accounts can read and write data into this account

  Below is some example code for managing access rights:

  {{{
  #!java

  /*
  The following code creates some new users, modifies their access rights, and then deletes them.

  For this code to work, you will need to:
  - Contact CDE to request that it be enabled. (It is not enabled by default for security reasons.)
  - Enable authentication for the app (from CDE's main panel)
  - Be logged in as an admin user, such as "admin". (You will receive the password of the account "admin" from us. You 
  can subsequently create additional admin accounts.)
  - Have a browser that equipped with a console, accessible through the javascript console object. The output will be printed to console.

  You may want to play around with this code in the chrome console, pasting it in paragraph by paragraph.
  */

  // Setup
  var cld_permissions = new DataObject('permissions'); // Set up the permissions object
  cld_permissions.addAction('newUser'); // Add the ability to add a new user
  cld_permissions.addAction('addRights'); // Add the ability to grant new rights to an existing user
  cld_permissions.addAction('changePass'); // Add the ability to set a user's password from the admin account
  cld_permissions.addAction('userList'); // Add the ability to list all users
  cld_permissions.addAction('checkAllPermissions'); // Add the ability to check all direct (_altUser) permissions a given account has
  cld_permissions.addAction('checkPermissions'); // Add the ability to check whether a given account has direct (_altUser) access to another
  cld_permissions.addAction('checkSecondaryPermissions'); // Add the ability to check whether a given account has secondary (_group) access to another
  cld_permissions.addAction('delAllRights'); // Add the ability to delete all of a user's access rights
  cld_permissions.addAction('delRights'); // Add the ability to delete all of a user's access rights to a specific account
  cld_permissions.addAction('delUser'); // Add the ability to delete a user

  // Add a user called admin2.(Later, this user will be granted admin rights)
  var cfg = {};
  cfg.data = {};
  cfg.data['newUser'] = 'admin2';
  cfg.data['pass'] = 'passAdmin2';
  cfg.successFunc = function(list, allData){
      logg('Done adding user '+cfg.data['newUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['newUser'].reload(cfg);

  // Add some more users, using the same configuration variable cfg as for admin2
  cfg.data['newUser'] = 'deptA';
  cfg.data['pass'] = 'passDeptA';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JohnSmith';
  cfg.data['pass'] = 'passJohnSmith';
  cld_permissions.act['newUser'].reload(cfg);
  cfg.data['newUser'] = 'JaneClark';
  cfg.data['pass'] = 'passJaneClark';
  cld_permissions.act['newUser'].reload(cfg);

  // Give admin2 read-write access to the admin group, thus giving it read-write access to all acounts
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'admin2';
  cfg.data['addedGroup'] = 'admin';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Assign JohnSmith and JaneClark to their department (deptA)
  // JohnSmith is department head, so he has read-write access. JaneClark only has read access.
  cfg.data['addedUser'] = 'JohnSmith';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '1';
  cld_permissions.act['addRights'].reload(cfg);

  cfg.data['addedUser'] = 'JaneClark';
  cfg.data['addedGroup'] = 'deptA';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cld_permissions.act['addRights'].reload(cfg);

  // Set a new password for JaneClark, to allow her to login
  var cfg = {};
  cfg.data = {};
  cfg.data['changedUser'] = 'JaneClark';
  cfg.data['pass'] = 'changedpass';
  cfg.successFunc = function(list, allData){
      logg('Done changing pass of user '+cfg.data['changedUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['changePass'].reload(cfg);

  // Get list of users
  var cfg = {};
  cfg.data = {};
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['userList'].reload(cfg);

  // Get all groups to which JaneClark has rw access
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkAllPermissions'].reload(cfg);

  // Check if JaneClark has read-write access to deptA. (She does not.)
  var cfg = {};
  cfg.data = {};
  cfg.data['checkedUser'] = 'JaneClark';
  cfg.data['checkedGroup'] = 'deptA';
  cfg.data['type'] = 'rw';
  cfg.successFunc = function(list, allData){
      logg(dump(allData));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkPermissions'].reload(cfg);

  // Check if JohnSmith has read access to JaneClark INCLUDING secondary access. (He does not.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // We want JohnSmith, as department head, to have read access to the accounts of all group members
  // So we assign deptA read rights to JaneClark
  var cfg = {};
  cfg.data = {};
  cfg.data['addedUser'] = 'deptA';
  cfg.data['addedGroup'] = 'JaneClark';
  cfg.data['read'] = '1';
  cfg.data['write'] = '0';
  cfg.successFunc = function(list, allData){
      logg('Done granting rights to user '+cfg.data['addingUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['addRights'].reload(cfg);

  // Now we check again if JohnSmith has read access to JaneClark INCLUDING secondary access. (He now does.)
  var cfg = {};
  cfg.params = {};
  cfg.params['checkedUser'] = 'JohnSmith';
  cfg.params['checkedGroup'] = 'JaneClark';
  cfg.params['type'] = 'read';
  cfg.successFunc = function(list, allData){
      logg(dump(list));
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['checkSecondaryPermissions'].reload(cfg);

  // Policy has changed, and we no longer want deptartment employees such as JaneClark to have any access to deptA
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.data['delGroup'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delRights'].reload(cfg);

  // JohnSmith is fired, so it is now necessary to delete his account and all his rights
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JohnSmith';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  // JaneClark is about to be moved to a new department, so all her existing rights are revoked
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing rights of user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delAllRights'].reload(cfg);

  // Clean-up: deletion of the remaining user accounts created for this demo
  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'JaneClark';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'admin2';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  var cfg = {};
  cfg.data = {};
  cfg.data['delUser'] = 'deptA';
  cfg.successFunc = function(list, allData){
      logg('Done removing user '+cfg.data['delUser']);
  };
  cfg.failureFunc = function(list, out){
      // out: is the full response and includes out['replyCode'] and out['replyText']. Available only if there was a response
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText'];
      logg('Failed: '+msg);
  };
  cld_permissions.act['delUser'].reload(cfg);

  }}}

  Below is some example code for accessing data in another account or group:

  {{{
  #!java

  /*
  The following code writes some data to a publicly accessible account, and then reads it again.

  For this code to work, you will need to:
  - Create a table called "test" from within CDE
  - Design this table with only one single user input, with id "val"

  Account _public_rw is used for this demonstration, since it exists by default

  */

  // Insert data
  var data={};
  data["ajaxAction"] = 'insert';
  data["_group"] = '_public_rw'; // In the case of groups scenario
  // Or: data["_altUser"] = 'aGroup'; // In the case of alternative users scenario
  data["val"] = 'a';
  cld_test.sendPending(data);
  cld_test.sendPendingAjax();

  // Read the data back again
  var cfg = {};
  cfg.params['_group'] = '_public_rw'; // In the case of groups scenario
  // Or: cfg.params['_altUser'] = '_public_rw'; // In the case of alternative users scenario
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out){
    logg("Error reading data.");
  }
  cld_test.act['select'].reload(cfg);

  }}}


name: FAQ
author: janette
ipnr: 58.165.201.133
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why this works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other. Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== What sql operations other than 'select' are available? ====
  Sql operations available by default for every cloud object are:
   * '''select''': an sql select
   * '''update''': updates a record, or inserts it if it does not already exist.
   * '''strictupdate''': an sql update. If the record with the given index does not exist, then does nothing (except print a warning to the console)
   * '''insert''': an sql insert
   * '''selectdistinct''': list all of the distinct values taken by a given column. Is equivalent to an sql "select distinct" applied to one column
   * '''delete''': deletes the record with the given index. This is not a definitive delete: the record can be undeleted.
   * '''undelete''': undeletes the record with the given index
   * '''deleteFinal''': definitively deletes a record. Corresponds to sql delete.


  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


name: FAQ
author: janette
ipnr: 58.165.201.133
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why this works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other). Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== What sql operations other than 'select' are available? ====
  Sql operations available by default for every cloud object are:
   * '''select''': an sql select
   * '''update''': updates a record, or inserts it if it does not already exist.
   * '''strictupdate''': an sql update. If the record with the given index does not exist, then does nothing (except print a warning to the console)
   * '''insert''': an sql insert
   * '''selectdistinct''': list all of the distinct values taken by a given column. Is equivalent to an sql "select distinct" applied to one column
   * '''delete''': deletes the record with the given index. This is not a definitive delete: the record can be undeleted.
   * '''undelete''': undeletes the record with the given index
   * '''deleteFinal''': definitively deletes a record. Corresponds to sql delete.


  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


name: FAQ
author: mnehme
ipnr: 77.42.137.43
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why this works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other). Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  == How can I process dialogue box data before it is sent to the server? ==
  Use the dialogueButtonPress event (see [CustomEvents]) to intercept the submission of the dialogue box. The listener function would look something like the following:

  {{{
  #!java
  function(ev){
  	// Event handler for dialogueButtonPress of this dialogue
  	if(!(ev.value=='submit')) return;
  	if(!(ev.id=='dialogues_cld_theObject_theAction_theDialogueName_1')) return;
  	
  	var data = currentView.data;
  	
  	// Do whatever processing you want to do with 'data'
  	
  	currentView.data = data;
  }
  }}}

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== What sql operations other than 'select' are available? ====
  Sql operations available by default for every cloud object are:
   * '''select''': an sql select
   * '''update''': updates a record, or inserts it if it does not already exist.
   * '''strictupdate''': an sql update. If the record with the given index does not exist, then does nothing (except print a warning to the console)
   * '''insert''': an sql insert
   * '''selectdistinct''': list all of the distinct values taken by a given column. Is equivalent to an sql "select distinct" applied to one column
   * '''delete''': deletes the record with the given index. This is not a definitive delete: the record can be undeleted.
   * '''undelete''': undeletes the record with the given index
   * '''deleteFinal''': definitively deletes a record. Corresponds to sql delete.


  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


name: FAQ
author: mnehme
ipnr: 77.42.137.43
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why this works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other). Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  == How can I process dialogue box data before it is sent to the server? ==
  Use the dialogueButtonPress event (see [CustomEvents]) to intercept the submission of the dialogue box. The listener function would look something like the following:

  {{{
  #!java
  function(ev){
  	if(!(ev.value=='submit')) return; // Check user did not click "Cancel" or another button
  	if(!(ev.id=='dialogues_cld_theObject_theAction_theDialogueName_1')) return; // Check that it is the right dialogue box (since all dialogues fire the same events)
  	
  	var data = currentView.data;
  	
  	// Do whatever processing you want to do with 'data'
  	
  	currentView.data = data;
  }
  }}}

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== What sql operations other than 'select' are available? ====
  Sql operations available by default for every cloud object are:
   * '''select''': an sql select
   * '''update''': updates a record, or inserts it if it does not already exist.
   * '''strictupdate''': an sql update. If the record with the given index does not exist, then does nothing (except print a warning to the console)
   * '''insert''': an sql insert
   * '''selectdistinct''': list all of the distinct values taken by a given column. Is equivalent to an sql "select distinct" applied to one column
   * '''delete''': deletes the record with the given index. This is not a definitive delete: the record can be undeleted.
   * '''undelete''': undeletes the record with the given index
   * '''deleteFinal''': definitively deletes a record. Corresponds to sql delete.


  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


name: FAQ
author: mnehme
ipnr: 77.42.137.43
text:
  [[PageOutline(1-5,Contents,inline)]]

  = Creating Specs and Prototyping =
  == How can I use CDE to create detailed Specs very fast? ==
  Very easily!
  1. Draw the views in Design view, and the dialogue boxes in Data view. 
  2. Go to Dynamics view, and click on any element that should be clickable. In the editor that appears, type instructions to the coder (between comments /*  */), and click Upload.
  3. Click on the Generate button, and send the link you get to the programmer... and you're done!

  What the programmer gets:
   * A prototype of the app, with all of the views, dialogue boxes, and sql databases already designed
   * Ability to navigate between the views via a clickable tree
   * Within each view, your instructions for that view in an easy to use table

  == How can I use CDE to prototype? Or as a basis for MVP/agile development ==
  The answer to the previous question shows how you can get a first prototype up and running in minutes.

  Simply keep on refining this prototype, and at any time you can click on the Generate button and have a running prototype!

  = Designing Views =
  == What is all this about inheritance? ==
  Views that are lower down in the hierarchy inherit from views that are higher up.

  Example: if you have two views called views/top/index.php and views/top/sub/index.php, then the second one will inherit:
   * The visual aspect of the first, provided that it (the second view) is completely empty of graphical elements
   * Any script events, or user functions defined in the first
   * Any properties of currentView

  == How can I create an element that resizes based on screen-size? ==
  This is not recommended. In order to make sure fonts are legible on all platforms, you should create one version of a view per platform. (For example, instead of creating a view called "Menu" you should create one called "Menu/iphone", then copy it into another called "Menu/ipad" and adjust as necessary).
  The differences in device screen-sizes are so great that if you do not do this, your text will not be legible.

  === But then I will have to duplicate scripts! ===
  No: simply create a view called "Menu" and write the script within that view. The views "Menu/iphone" and "Menu/ipad" will inherit from it. So any script that you put in "Menu" will apply to "Menu/iphone", and any design elements you put in "Menu" will be visible as a background to the view "Menu/iphone"

  === But I still want to do it. ===
  If you insist:
  Alternative 1: Create a script fired by the viewOpenComplete event, which automatically resizes the div based on screen size. For this you’ll find it helpful to give the div a classname via the fill dialogue box.
  Alternative 2: 
   * Create the app for one screen size. In the following, we will assume it is the ipad screen size.
   * Once it is created, download the html, and edit the scale in the viewport meta-tag. Set it as follows: <meta name="viewport" id="viewport" content="width=device-width, initial-scale=0.42, maximum-scale=0.42, minimum-scale=0.42" />. This will create an html file that is resized to fit in the iphone. You may want to adjust the 0.42 factor slightly
   * The above will suffice to resize to iphone size. But in order for it to look good, you’ll also want to center it. Do so by adding <div style='position:relative;left:-5px;width:1024px;margin:0 auto;'>  around the entire content (but within the <body> tags) (An explanation of why this works is provided at [http://buildinternet.com/2008/11/how-to-center-a-website-with-css/])
   * Use the new html file to create a separate binary for the iphone. (Apple App Store Universal apps are generally a bad idea: they are useless since high ranking on ipad does not help iphone rank, or vice-versa. Furthermore, they force you to price your ipad app at the same price as the iphone one, even though willingness to pay is generally different.)

  == How do I align/distribute elements? ==
  First group them (by rapidly clicking on one after the other). Then select the group (it appears as a beige-bordered transparent container). Finally, open the "Position" menu and select a menu option. Optionally, you can ungroup with the "Ungroup" button (upper-right).

  == How do I insert a <a> link? ==
  It is better to create a clickable element. To do this:
   * Create the element in Design view
   * Go to Dynamics view
   * Simply click on it
   * In the script box, type: location.href = 'http://www.google.com'; 
  (Of course, replace http://www.google.com with whatever address you wish.)
   * Upload the script

  = Creating Tables and Dialogue Boxes =
  == How can I create a database table? ==
  Simply create a dialogue box by following the instructions in the "Data" tab.
  Once you click "Go!", you will be taken to a design interface for the dialogue box.
   * Add graphic elements as desired.
   * Add user-input elements: one per intended table column. You can create these elements via the dialogue box in the menu: "Shapes, Images, Buttons" -> "User Input"
   * Create the "ok" button: create another user input element and give it the type “submit” and the name “_submit”, and the default value “Ok” (or whatever other default value you want)
   * Create the cancel button: Create another user input element and give it the type “submit” and the name “_cancel”, and the default value “Cancel” (or whatever other default value you want)
  And you’re done! If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.
  '''Best practice:''' on viewOpenComplete of the top view, use a setInterval call to execute cld_Restaurants.sendPendingAjax() once every 60000 milliseconds (and do the same for every table you create.) This way you will not have to manually execute sendPendingAjax() every time.

  === How can I add a date column to the table, for the insertion date? ===
  No need! It is added anyway, and is called 'dateCreated'!

  Also, if you need to know the update/edit date of a specific column in the table, consult the column called [Column Name]DateModif. For example if the column is called restaurants, consult restaurantsDateModif to determine the last time it was updated/edited.

  === How can I add a unique index? ===
  No need! Each new record automatically receives a unique index, in column 'index'

  If for any reason, you need, for your own purposes, the ability to generate a unique index, the genIndex() function can be used. This function has a key benefit: it avoids collision of indexes generated on multiple devices, if copies of your app are running on multiple devices without communicating.

  == How can I create a dialogue box? ==
  Do it by creating a table, as described above. To add buttons, do the following:
   * Ok button: create a user input of tag name 'input', type 'submit' and id (database field name) '_submit'. As you can see, tag name and type are just like in html.
   * Cancel button: create a user input of tag name 'input', type 'submit' and id (database field name) '_cancel'. As you can see, tag name and type are just like in html.
   * Custom button: create a button or other type of element, and use javascript to add events to it when event dialogueOpen (attached to Dialogue.events) fires

  If you want to create a second dialogue box corresponding to the same table, here is how:
   * From any one of the tabs ("Design", "Data" or "Dynamic"), click on the name of the current view on the top left of the screen. This takes you to the view selection page.
   * Click on the "New Dialogue" button.
   * Design the dialogue box, as described above.

  == How can I process dialogue box data before it is sent to the server? ==
  Use the dialogueButtonPress event (see [CustomEvents]) to intercept the submission of the dialogue box. To find it:
  1. Select the page within which the dialogue is intended to appear. (NOT the dialogue box itself)
  2. Open it in Dynamics Tab
  3. Click on the "Page" button to add an event to the page. The script editor will then open.
  4. In the events selection drop-down, select dialogueButtonPress

  The listener function would look something like the following:

  {{{
  #!java
  function(ev){
  	if(!(ev.value=='submit')) return; // Check user did not click "Cancel" or another button
  	if(!(ev.id=='dialogues_cld_theObject_theAction_theDialogueName_1')) return; // Check that it is the right dialogue box (since all dialogues fire the same events)
  	
  	var data = currentView.data;
  	
  	// Do whatever processing you want to do with 'data'
  	
  	currentView.data = data;
  }
  }}}

  = Scripting =
  == How can I move from view to view? ==
  By using the function gotoView()

  For example: 
   * Imagine that you have created a view at views/newview/index.php. In that case, this view will have an object associated with it, called: views_newview_index (Just replace the slashes with underscores, and remove the extension.)
   * You can open this view with the code: gotoView(views_newview_index)

  == How can I programatically access an html element? ==
  In Design view, give the element a class name (by selecting it and clicking on the Format button). Then you will be able to access using one off the two alternatives in the code below. Note that assigning an id is not supported in order to avoid the bugs that come from assigning the same id to multiple elements (which violates uniqueness of ids).

  {{{
  #!java
  /*
  Accessing an element of class name "aClass"
  */

  document.getElementsByClassName("aClass")[0]; // Alternative 1
  getc("aClass")[0]; // Alternative 2: getc is a CDE shortcut to getElementsByClassName

  }}}

  == How can I programatically access the cloud data? ==
  === To access the data in the table ===
  For example, let us assume you named the dialogue box "Restaurants" (when creating the table). Your cloud object is called cld_Restaurants.  Here is the script.
   * If you want the script to get the data from the remote database: use the cloud’s reload method:
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  cld_Restaurants.act['select'].reload(cfg);
  }}}
   * If you want the script, while waiting for the data to arrive from the remote database, to draw on the local cache: use the cloud’s data access method (getData) 
  {{{
  #!java
  var cfg = {};
  cfg.successFunc = function(list){
  // Do something with “list”
  // “list” is an array of records. Each record is an object of the form
  // { colName1: "value of column 1", colName2: "value of column 2", colName3: "value of column 3", [etc……]}
  };
  // cfg.where = [["location","=",theLocation], ["type","=","Lebanese"]]; // An optional where clause
  var list0 = cld_Restaurants.act['select'].getData(cfg);
  // Then do something with list0, which contains the local cache data
  }}}

  ==== How about adding an sql where clause? ====
  SQL is not supported (except on request), for security reasons. However where clauses are supported, as follows. Simply add a line similar to the below, to the code examples already given:
  {{{
  #!java
  cfg['where'] = [['col1', '=', 'value 1'], ['col2', '=', 'value 2']]; // For security reasons, only a limited range of 
  // operators are supported: "=", "<", ">", ">=", "<=", "!="
  }}}

  ==== What sql operations other than 'select' are available? ====
  Sql operations available by default for every cloud object are:
   * '''select''': an sql select
   * '''update''': updates a record, or inserts it if it does not already exist.
   * '''strictupdate''': an sql update. If the record with the given index does not exist, then does nothing (except print a warning to the console)
   * '''insert''': an sql insert
   * '''selectdistinct''': list all of the distinct values taken by a given column. Is equivalent to an sql "select distinct" applied to one column
   * '''delete''': deletes the record with the given index. This is not a definitive delete: the record can be undeleted.
   * '''undelete''': undeletes the record with the given index
   * '''deleteFinal''': definitively deletes a record. Corresponds to sql delete.


  ==== But I really, really need to be able to access the sql myself! ====
  See the section on Direct Server Access in [wiki:Advanced].

  === To input data into the table ===
  The easiest way is to create a dialogue box (as described elsewhere) and do no scripting. If the dialogue was called (for example) dialogues/cld_Restaurants_insert_table.php, then you can programmatically open it via: dialogues_cld_Restaurants_insert_table()
  Once the user clicks “Ok” on the dialogue box, the data is automatically stored to be sent to the remote database.
  Whenever you want to send, simply execute cld_Restaurants.sendPendingAjax() (This works even if you don’t have internet, it will simply do a smart sync once you get back online.) This step is separate in order to avoid causing bandwidth problems. Often users will make minor changes to data they just input.

  Or you can do it programmatically as follows:
  {{{
  #!java
  var data={};
  data["ajaxAction"] = 'insert'; /* Other options: update, delete, undelete, deleteFinal. You can also use select and selectdistinct, however the reload and getData methods do this a lot better */
  data["Column 1 name"] = aValue;
  data["Column 2 name"] = aValue2;
  data["Column 3 name"] = aValue3;
  cld_Restaurants.sendPending(data);//This stores the data in the local cache, ready to be sent to the server
  cld_Restaurants.sendPendingAjax();//This forces all pending data to be synced with the server.
  /* For advanced users: set up an event handler to handle event sendPendingFailure. Do this via the following code:
  addEvent(cld_Restaurants.events, 'sendPendingFailure', function(ev){
  // “ev” has the form: {type:"sendPendingFailure",item:i,attempts:numberOfattempts,status:statusCode,
  // code:ajaxStatusCode,msg:"The error message."}. 
  // The “attempts” field is not there for all types of errors.
  });
  */
  }}}

  Finally, a more complex option is to create a dialogue box as described, but instead of just simply letting it do its job, intercept the user input, modify it, and then send it on. Here is how:
   * Create the dialogue box as described above
   * Create a function to handle the user input – something like the below:
  {{{
  #!java
  //function(ev){ // Commented out because, as you’ll see in a bit, it’s already there
        // Event handler for a dialogue box
        if(!(ev.value=='submit')) return;// If it’s not the submit button, do nothing
        if(!(ev.id== dialogues_cld_Restaurants_insert_table_1')) return; /* If it’s not instance 1 of the dialogue box dialogues_cld_Restaurants_insert_table then do nothing */
        
        var data = currentView.data; // “data” is now an associative array containing the dialogue box data
        
        // Now do something with the data
  /* For example, the below combines two dialogue box fields, called dialogueField1 and dialogueField2, into a single field */
        data['newField'] = data['dialogueField1']+" "+ data['dialogueField2'];
        delete data['dialogueField1'];
        delete data['dialogueField2'];

        currentView.data = data;// Once the data has been processed, overwrite the original data with the modified data
  //}// Commented out because, as you’ll see in a bit, it’s already there
  }}}
  Now all you need to do is go to the view in which the dialogue appears, go to the "Dynamics" tab, click on “Current View”, select the event dialogueButtonPress and copy-paste the function in there.
  WARNING: the lines “function(ev){” and the closing “}” are already there, so they shouldn’t be in your function!


  == How can I graph the cloud data into nice tables, trees, pie charts, bar charts, etc.? ==
  {{{
  #!java
  /*
  The following code illustrates creation of a graph.

  For this code to work, you will need:
  - A table called "data", with some data inserted into it, and a column called colA
  - An html element of class graphPane, which will contain the table/graph
  */

  var cfg = {};
  cfg.successFunc = function(list){
      alert("Successfully got graph data.");
      var cfg={};
      cfg.pane = getc('graphPane')[0].id;// The graph should be displayed in the element of class graphPane

      // By default, this will display a table. Uncomment one of the lines below if you want something else:
      // cfg.displayType = 'tree'; // If your data consists of one single column, and the data values are of directory form (blabla/blabla/blabla), then this will display them as a tree
      // cfg.displayType = 'chart'; cfg.chartType = 'pie'; // A google graphs chart. chartType supported values are: pie, bar, line
      // cfg.displayType = 'self'; // Self-formatted. In other words, formatting columns are used, such as "top", "left", "backgroundColor", "innerHTML", etc.

      // Program some sort of user interaction, e.g. ability to tap on rows of a table
      cfg.eventType = 'tapend'; // Tapend need not be specified: it is the default
      cfg.eventHandler = function(ev){
          // Whatever it is you want to happen when the user interacts via eventType (e.g. clicks on a table cell)
          alert(this.firstChild.innerHTML);//alerts the contents of the clicked cell
          // console.warn(dump(ev)); // If you feel like exploring
      };

      // Create the table, graph, pie, or whatever it is
      var dlist = new DataList(list,cfg); // Create the object that represents it
      var cfg2={}; // Allows setting some parameters
      // cfg2.sort = DataList.sortAZ('colA'); // For tables, allows sorting by the contents of column A. cfg2.sort can be set to your own custom sorting function. DataList.sortAZ is a default simple sort. It supports reverse sorting via: DataList.sortAZ('colA', true)
      dlist.display(cfg2); // Display!
  };
  cld_data.act['select'].reload(cfg); // Get the data to be displayed

  }}}

  Here is an example specifically for google charts:
  {{{
  #!java
  /*
  Create a pie chart out of some data.

  For this code to work, you will need:
  - An html element of class graphPane, which will contain the table/graph
  */

  // Set up the data.
  dataArr = [["Answers","Responses"],["1",5],["2",0],["3",0],["4",2],["5",1],["6",0],["7",0],["8",0],["9",1],["10",2]];
  var cfg={};
  cfg.pane = getc('graphPane')[0].id;
  cfg.displayType = 'chart';
  cfg.chartType = 'pie';
  var dlist = new DataList(dataArr, cfg);
  dlist.display();
  }}}

  === When I display the data as a table, can I modify which columns are displayed? Rename columns? ===
  Yes!
  {{{
  #!java
  var list = [{"Index":1,"Col1":"111","Col2":"222","HiddenCol3":"333","Col4":"444"},{"Index":2,"Col1":"123","Col2":"456","HiddenCol3":"789","Col4":"012"}];
  var cfg2={}; cfg2.pane = getc("adminResponseList")[0].id;
  cfg2.alias={Col1:'Col1',Col2:'Col2'}; 
  cfg2.doNotDisplayByDefault = true; // This option causes only columns that are in cg2.alias to be displayed. If not set, all 
  // columns will be displayed except those renamed to '' in alias (as in cfg2.alias = {HiddenCol3:''})
  var dlist = new DataList(list,cfg2);tb=dlist.display(); tb.style.visibility='visible';
  }}}

  == How can I programatically control rejected insert/update/deletes of data by a user? ==
  Note: to understand the below, you will need to be familiar with advanced user-access rights management using _group and _altUser.

  When the app tries to access data and the user (or _group or _altUser) does not have access rights to the requested data, two events will fire: sendPendingFailure and !NoAuthForThisContent. You can choose to catch either or both events. In the example of trying to insert something without the appropriate permissions, you may want to catch the event !NoAuthForThisContent, delete the pending record from the cache and inform the user that they were unsuccessful at inserting as they didn't have the appropriate permissions with something like:

  {{{
  #!java
  var data = {};
  data["ajaxAction"] = 'insert';
  data["departmentName"]= 'blah';
  data["_group"] = "admin"; 
  cld_department.sendPending(data);
  cld_department.sendPendingAjax();

  addEvent(authEvents, "NoAuthForThisContent", function(ev){
     localStorage.removeItem(ev['recordKey']); // deletes the record from the cache
     alert("Your insert failed as you do not have permission to insert these records"); // you would replace this with a message displayed within your app as alerts look ugly
  });

  }}}

  = Facebook Integration =
  == How can I integrate with Facebook? ==
  To enable Facebook login, simply go to the main panel, and in the "Facebook" section fill in the App ID and App Secret. It's as easy as that! See [wiki:Advanced] section for some additional things you can do.


