# wiki

name: FAQ
author: mnehme
ipnr: 212.36.209.169
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate.
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: FAQ
author: mnehme
ipnr: 77.42.140.107
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: FAQ
author: mnehme
ipnr: 77.42.140.107
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS unless you also write your own validation function to do its job.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: FAQ
author: mnehme
ipnr: 178.135.132.97
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS unless you also write your own validation function to do its job.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" etc.
      // cfg.displayType = 'csv'; // Allows download of the data in csv form

      // 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: FAQ
author: mnehme
ipnr: 178.135.40.217
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS unless you also write your own validation function to do its job.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" etc.
      // cfg.displayType = 'csv'; // Allows download of the data in csv form

      // 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.script0: A function to be applied to each data record ''before'' it is displayed. The function receives as argument the data object (of the form {field1:value1, field2:value2, ...}). It then can modify this object in any way that is desired.
  In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: WikiStart
author: mnehme
ipnr: 178.135.40.217
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. You can also use it along with your favorite library (JQuery, Google Charts, etc.)
   * '''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:Addons III. Add-ons] -- Add-ons
   * [wiki:Scripting IV. Scripting] -- Scripting
   * [wiki:PhoneGap V. Building in PhoneGap/Cordova] -- Building in !PhoneGap/Cordova
   * [wiki:Advanced VI. 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
   * Reference
    * [wiki:ServerResponseCode Server Response Codes] -- Server Response Code Reference
    * [wiki:CustomEvents Events] -- Events Reference
   * [wiki:TracStartingPage Bug-reporting assistance] -- You can report bugs and request features using Trac. This links to the Trac documentation.


name: Addons
author: mnehme
ipnr: 178.135.40.217
text:
  = Overview =
  ''Note: this feature is still in beta.''

  Add-ons allow fast import of extra functionality. This comes on top of the speed boost provided by CDE itself.

  = Loading add-ons into your app =
   * From the main panel, click on "Add or manage add-ons" in order to go the add-on management view.
   * The list of add-ons available for loading will then appear on the left
   * Note that most add-ons have a script which automatically runs every time your new app is fired, in order to do any setup the add-on may require. It is called installerScript, and can be edited if need be. This is one of the options available in the table showing add-ons that have already been loaded.

  = Managing add-ons that have already been loaded =
   * From the main panel, click on "Add or manage add-ons" in order to go the add-on management view.
   * The list of add-ons already loaded will then appear on the right, along with options to manage them. Note that if you load an add-on twice, it will appear in this list a second time with v2 written next to it.

  = Creating an add-on =
   * From the main panel, click on "Add or manage add-ons" in order to go the add-on management view.
   * A link labeled "Export this app as an add-on..." will allow you to turn the current app into an add-on, sharing it with all users. 

  Note: not all features are exportable. To summarize, almost everything except css and functions in the file userDefined.js gets exported. For details, see below.

  Export includes:
   * All pages and dialogues
   * Scripts
   * Graphic elements (both html and images, as well as element groups)
  It does not include:
   * Copying of image files that are inserted via script/function only, and not via the GUI
   * Any references to img elements in scripts and user functions that are not plain vanilla. (So "img/arrow4v.png" will be recognized, but not: 
  {{{
  str="img/"; str+="arrow4v5.png"; // str will not be recognized as an image and so may not be properly exported
  }}}
   * css, functions in the userDefined.js file, and other special files.

  Later, when an import occurs, pages and user functions will be renamed intelligently. Renaming MAY not work in unusual or borderline cases, so it is safer to stick to regular syntax.


name: Advanced
author: mnehme
ipnr: 178.135.147.81
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:
   * All functions must be named using the convention ajax_[name of ajaxObj]_[name of ajaxAction](). They are then accessible from within your app via the standard cloud functions, using the ajaxObj and ajaxAction that you defined
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql). ajaxSqlQuery($sql, true) returns an array of output, error message (if relevant), and number of rows affected
   * 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, do as in this example: 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 default functionality with some of your own.

  CDE default login functionality is part of an add-on called LoginInterface, and as such can be edited exactly like your own code. For example, to open the user scripts, go to views/LoginInterface/index.php in Dynamics tab and click on "Page" button, or any other element on the page. Below are brief descriptions of the default CDE functions which you will find there:
   * 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

  == 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);

  }}}

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

  This feature is only activated on request. It allows you to control which parts of the app an already logged in user is allowed to access. It is based on control of data flow: if a user tries to access an area containing server data to which his access codes do not give him access, then he will receive an error message stating that he does not have access. The area will still display, but without the data. This feature also restricts writing of data.

  This feature is useful either for limiting an authorized user's rights; or for managing collaboration. In the latter scenario, each collaborator gets an authorization code for the section he is working on. This prevents two different collaborators from accidentally overwriting each others' changes.

  === Accessing another CDE account ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  This feature is only activated on request. Its purpose is to allow teams to work on an app. In order to activate it, contact us with the list of the team members' usernames. We will create a group for you, which you can share.

  In order to access the group:
  1) Log in to your account
  2) Add to the end of the http address: {{{?_altUser=[insert group name here]}}}

  In order to stop accessing the group:
  1) Logout
  2) Log back in again

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

  Note: You are unlikely to require this feature. For most apps, the default settings will easily accommodate your user base. 

  Some apps have particularly heavy database or data processing requirements, and as a result require scaling. Below are some of the areas where you can scale up:
   * Database: If your database processing is intensive, you can optimize the database, which is mysql. This requires direct access to the database, which is a premium feature.
   * PHP memory limit, or PHP execution limit. These also can be scaled up for you (also a premium feature).
   * localStorage: if you hit a localStorage limit, you can empty the caches for each cloud object as required. For example the following will clear all of the cached data of object cld, except for data which has not yet been synced with the server: "cld.pendingCache.clearAll(); cld.pendingSentCache.clearAll();". Also, if necessary, when calling reload() and getData(), by setting "cfg.doNotWriteToLocalStorage = true" you can prevent the values returned by the server from ever being written into the localStorage. (Free feature.)


name: ServerResponseCode
author: mnehme
ipnr: 178.135.224.151
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]]
  622: Unsafe SQL error[[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]]
  638: Page/location access denied.[[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[[BR]]
  701: Error in custom php functions by customer[[BR]]


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

  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Logout
   * Force refresh (ctrl-F5)
   * Log back in again
  This clears up issues due to the browser.

  = 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.

  == My PHP edits don't appear to be being taken into account. ==

  If you have requested the advanced feature for Direct Server Access via PHP, having uploaded a PHP file with the uploader, you must regenerate from the home screen of CDE to force a full regeneration. A partial regeneration will not add the PHP changes.

  == Debug mode says I have multiple IDs ==

  If you use the same user defined input name (id) for more than one user defined input on different views you will get an error saying you have multiple IDs. It is good practise to prefix your user input id to reflect the view it belongs to so this doesn't happen. Eg if your view was for adding filters your might call your inputs addFilOKbtn and addFilCancelbtn etc. But remember not to make the names too long as there is a limit to the length of scripts for an event and you don't want to take it all up with the names of inputs.

  == I can't see the error message I setup in my dialogue for the user ==

  If when using a dialogue, you set a message in the validFunc for the dialogue, to provide feedback to the user, eg if they leave a field blank during an insert you need to access it via

  {{{
  getc("errMsgFieldName")[1].innerHTML = "the message";
  }}}

  as the dialogue box that is visible is the second instance and therefore you need to use index 1 instead of the usual 0 to access it.

  == When I click on an element it doesn't execute the code that I assigned to it ==

  You may have added a click event or the like to an element but it is not executing it. Have you checked the z-index of the element, that it is at the top and there not being shielded from your click. The z-index of elements can be changed in design view from the Position menu by clicking on the "move to front" and "move to back" buttons. For more information see the section on the Position Submenu in [wiki:Menus#PositionSubmenu]

  == 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.

  == Buttons are not working ==
  This issue is typically due to having another element with the id of the button somewhere on the page (or two buttons with the same id within the dialogue box). As a result, the click event intended for the button ends up somewhere else. The solution is to eliminate the duplicate id.

  Sometimes the above happens because you copy an "Ok" button, modify the text to "Cancel," and forget to turn it into a cancel button (using the "Insert Shapes, Buttons and User inputs" menu). So though it appears you have one "Ok" button and one "Cancel" button, in reality you have two "Ok" buttons interfering with each other.

  = 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 in all contexts. Using it is likely to cause malfunctions. As for "-", "+", "/", etc. they should not be used in naming. Nor should any html code such as &#47; be used as a way around this.

  == 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 "_", or any field name that is a mysql command (select, where, etc.). 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.

  == Have you used the same field name in more than one table which each use dialogues, or do you have more than one dialogue defined for the same table, or do you have a user defined input with the same name as a field name in one of your tables? ==

  If you have more than one table with a common field name and you use a dialogue for each, or if you have more than one dialogue defined for one table, or you have a user defined input id which is the same as a field name in a table,  a clash can occur, as the field name becomes the ID of the input element and in CSS an ID must be unique. CDE deals with this by assigning any possible duplicates to id2. To overcome mutliple tables with the same field name it is good practise to prefix the column name in relation to the table name. Similarly, name user defined inputs in relation to the view they appear in.

  == 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: FAQ
author: mnehme
ipnr: 178.135.122.58
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS unless you also write your own validation function to do its job.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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
   * '''selectOuterJoin''': returns the result of an outer join with another table. Below is a code example illustrating it:
  {{{
  #!java
  /*
  * Does an outer join of table restaurants with table joinedTable
  */
  cld_restaurants.addAction('selectOuterJoin');
  var cfg = {}; cfg.params = {}
  cfg.params['joinedTable'] = 'anotherTable';
  cfg.params['onCondition'] = '`restaurants`.`jointField`=`anotherTable`.`jointField`';
  cfg.where = [["`restaurants`.`rank`","=",1],["`restaurants`.`location`","=","Boston"]];//Optional where clause
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list,out,resp){
    logg("Error reading data. Http response was "+resp);
  }
  cld_restaurants.act['selectOuterJoin'].reload(cfg);
  }}}
   * '''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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" etc.
      // cfg.displayType = 'csv'; // Allows download of the data in csv form

      // 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.script0: A function to be applied to each data record ''before'' it is displayed. The function receives as argument the data object (of the form {field1:value1, field2:value2, ...}). It then can modify this object in any way that is desired.
  In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: Advanced
author: mnehme
ipnr: 178.135.122.58
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:
   * All functions must be named using the convention ajax_[name of ajaxObj]_[name of ajaxAction](). They are then accessible from within your app via the standard cloud functions, using the ajaxObj and ajaxAction that you defined
   * Arguments are passed via $_POST only
   * Writing to sql is done via: ajaxSqlQuery($sql). ajaxSqlQuery($sql, true) returns an array of output, error message (if relevant), and number of rows affected
   * 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, do as in this example: 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 default functionality with some of your own.

  CDE default login functionality is part of an add-on called LoginInterface, and as such can be edited exactly like your own code. For example, to open the user scripts, go to views/LoginInterface/index.php in Dynamics tab and click on "Page" button, or any other element on the page. Below are brief descriptions of the default CDE functions which you will find there:
   * 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

  == 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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  cld_test.act['select'].reload(cfg);

  }}}

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

  This feature is only activated on request. It allows you to control which parts of the app an already logged in user is allowed to access. It is based on control of data flow: if a user tries to access an area containing server data to which his access codes do not give him access, then he will receive an error message stating that he does not have access. The area will still display, but without the data. This feature also restricts writing of data.

  This feature is useful either for limiting an authorized user's rights; or for managing collaboration. In the latter scenario, each collaborator gets an authorization code for the section he is working on. This prevents two different collaborators from accidentally overwriting each others' changes.

  === Accessing another CDE account ===
  [[Image(gold-star-md.png, 16, 16)]] Premium Feature

  This feature is only activated on request. Its purpose is to allow teams to work on an app. In order to activate it, contact us with the list of the team members' usernames. We will create a group for you, which you can share.

  In order to access the group:
  1) Log in to your account
  2) Add to the end of the http address: {{{?_altUser=[insert group name here]}}}

  In order to stop accessing the group:
  1) Logout
  2) Log back in again

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

  Note: You are unlikely to require this feature. For most apps, the default settings will easily accommodate your user base. 

  Some apps have particularly heavy database or data processing requirements, and as a result require scaling. Below are some of the areas where you can scale up:
   * Database: If your database processing is intensive, you can optimize the database, which is mysql. This requires direct access to the database, which is a premium feature.
   * PHP memory limit, or PHP execution limit. These also can be scaled up for you (also a premium feature).
   * localStorage: if you hit a localStorage limit, you can empty the caches for each cloud object as required. For example the following will clear all of the cached data of object cld, except for data which has not yet been synced with the server: "cld.pendingCache.clearAll(); cld.pendingSentCache.clearAll();". Also, if necessary, when calling reload() and getData(), by setting "cfg.doNotWriteToLocalStorage = true" you can prevent the values returned by the server from ever being written into the localStorage. (Free feature.)


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

  = First Thing To Do =
  The first thing to do if you have an issue is to:
   * Logout
   * Force refresh (ctrl-F5)
   * Log back in again
  This clears up issues due to the browser.

  = 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.

  == My PHP edits don't appear to be being taken into account. ==

  If you have requested the advanced feature for Direct Server Access via PHP, having uploaded a PHP file with the uploader, you must regenerate from the home screen of CDE to force a full regeneration. A partial regeneration will not add the PHP changes.

  == Debug mode says I have multiple IDs ==

  If you use the same user defined input name (id) for more than one user defined input on different views you will get an error saying you have multiple IDs. It is good practise to prefix your user input id to reflect the view it belongs to so this doesn't happen. Eg if your view was for adding filters your might call your inputs addFilOKbtn and addFilCancelbtn etc. But remember not to make the names too long as there is a limit to the length of scripts for an event and you don't want to take it all up with the names of inputs.

  == I can't see the error message I setup in my dialogue for the user ==

  If when using a dialogue, you set a message in the validFunc for the dialogue, to provide feedback to the user, eg if they leave a field blank during an insert you need to access it via

  {{{
  getc("errMsgFieldName")[1].innerHTML = "the message";
  }}}

  as the dialogue box that is visible is the second instance and therefore you need to use index 1 instead of the usual 0 to access it.

  == When I click on an element it doesn't execute the code that I assigned to it ==

  You may have added a click event or the like to an element but it is not executing it. Have you checked the z-index of the element, that it is at the top and there not being shielded from your click. The z-index of elements can be changed in design view from the Position menu by clicking on the "move to front" and "move to back" buttons. For more information see the section on the Position Submenu in [wiki:Menus#PositionSubmenu]

  == 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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      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.

  == Buttons are not working ==
  This issue is typically due to having another element with the id of the button somewhere on the page (or two buttons with the same id within the dialogue box). As a result, the click event intended for the button ends up somewhere else. The solution is to eliminate the duplicate id.

  Sometimes the above happens because you copy an "Ok" button, modify the text to "Cancel," and forget to turn it into a cancel button (using the "Insert Shapes, Buttons and User inputs" menu). So though it appears you have one "Ok" button and one "Cancel" button, in reality you have two "Ok" buttons interfering with each other.

  = 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 in all contexts. Using it is likely to cause malfunctions. As for "-", "+", "/", etc. they should not be used in naming. Nor should any html code such as &#47; be used as a way around this.

  == 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 "_", or any field name that is a mysql command (select, where, etc.). 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.

  == Have you used the same field name in more than one table which each use dialogues, or do you have more than one dialogue defined for the same table, or do you have a user defined input with the same name as a field name in one of your tables? ==

  If you have more than one table with a common field name and you use a dialogue for each, or if you have more than one dialogue defined for one table, or you have a user defined input id which is the same as a field name in a table,  a clash can occur, as the field name becomes the ID of the input element and in CSS an ID must be unique. CDE deals with this by assigning any possible duplicates to id2. To overcome mutliple tables with the same field name it is good practise to prefix the column name in relation to the table name. Similarly, name user defined inputs in relation to the view they appear in.

  == 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: FAQ
author: mnehme
ipnr: 178.135.122.58
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS unless you also write your own validation function to do its job.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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
   * '''selectOuterJoin''': returns the result of an outer join with another table. Below is a code example illustrating it:
  {{{
  #!java
  /*
  * Does an outer join of table restaurants with table joinedTable
  */
  cld_restaurants.addAction('selectOuterJoin');
  var cfg = {}; cfg.params = {}
  cfg.params['joinedTable'] = 'anotherTable';
  cfg.params['onCondition'] = '`restaurants`.`jointField`=`anotherTable`.`jointField`';
  cfg.where = [["`restaurants`.`rank`","=",1],["`restaurants`.`location`","=","Boston"]];//Optional where clause
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list, out, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  cld_restaurants.act['selectOuterJoin'].reload(cfg);
  }}}
   * '''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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" etc.
      // cfg.displayType = 'csv'; // Allows download of the data in csv form

      // 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(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.script0: A function to be applied to each data record ''before'' it is displayed. The function receives as argument the data object (of the form {field1:value1, field2:value2, ...}). It then can modify this object in any way that is desired.
  In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: FAQ
author: mnehme
ipnr: 178.135.102.28
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 follow 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? ==
  If you would like to simultaneously create a corresponding database table, follow the instructions for creating a database table. This will also create a dialogue box.

  If you want to create an additional dialogue box for an existing table, or a dialogue box that is not linked to a table then do the following:
   * Go to the Page Select view. (This can be done by clicking on the name of the currently edited page, which appears on the top left, in Design view.)
   * Click on the "New Dialogue" button
   * Fill in the fields:
      * "Name": the name you give the dialogue box to allow you to identify it. (Note that this will not be the full name of the dialogue. The full name will be of the form dialogues/cld_[table name]_[action]_[name]) 
      * "Table": the table the dialogue box is linked to. Data input into the dialogue box will be written into this table.
      * "Action": is the purpose of the dialogue box to insert new data records? Or to update existing data records?
   * If you do not want the dialogue box to be linked to a table, then you will need to rename it to something of the form "dialogues/cld!__[name]". This can be done from the "File" menu of the Design view.
   
  Then, to add user input elements and buttons, do the following in the Design view:
   * Create an element (by drawing a rectangle)
   * Click on "Insert !Textbox/User Input" 
   * From the options that appear, select the one you want. If you select "Custom User Input..." a dialogue box appears.

  You can also create elements via javascript. Here is some info necessary to create certain types of buttons from within javascript:
   * 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: simply create a button or other type of element

  == How do I change the tabbing order of dialogue box elements?
  Select the element, then click on "Insert !Textbox/User Input"->"Custom User Input...", and put a number in the "Tabbing Order" field. For example, if you give a tabbing order of 10 to your first element, 20 to your second element, and 25 to your third element, then when you tab, the cursor will cycle from your first element to your second, and from your second to your third.

  == How do I work with a dialogue box? ==
  For example, how do I open the dialogue box at a particular position, or execute some code before it opens (using event dialogueOpen) etc.

  {{{
  // First lets open the dialogue with id shortDialogueName around the point of coordinates (pageX:270,pageY:300)
  // Notes:
  // - The argument can be an event (e.g. a click event), in which case the dialogue box will open around where the click occurred
  // - You can also set xOffset and yOffset. These indicate the offset between the top left corner of the dialogue, and the point around 
  // which the dialogue is opened. They are also useful when the argument is an event, for shifting the position away from the event 
  // location by a certain amount. In the latter case, since event objects are read-only, you will need to first copy the object as in ev2=copy(ev); ev2.xOffset=-100; ev2.xOffset=100;
  // When not set, xOffset defaults to -50, and yOffset defaults to -60

   dialogues_cld_yourTableName_insert_yourDialogueName({pageX:270,pageY:300,dialId:'shortDialogueName'}) 
   // Now you can access:
   // - The dialogue box div via get('shortDialogueName')
   // - The dialogue box object via window['dial']['shortDialogueName']
   // - The triggering configuration argument via window['dial']['shortDialogueName'].ev; If the triggering argument is an event, it is still accessed in the same way
   // - The input elements via their html id: get(id).value
   // Also, you can set/modify the properties:
   // - window['dial']['shortDialogueName'].validFunc: a validation function called when the dialogue is submitted. If it returns false, the submit will not proceed.
   // - window['dial']['shortDialogueName'].validFunc0: (RECOMMENDATION: DO NOT TOUCH THIS unless you also write your own validation function to do its job.) This is the CDE default validation function. It primarily checks that the inputs are not longer than the corresponding database fields. Set it to undefined in order to disactivate. If you want to change the position or size of the error message, it can be done with the stylesheet via class "error"
   // - window['dial']['shortDialogueName'].func: a function to be called on pressing any button, EXCEPT if its id is _cancel, or if data['_abort'] is set to true
   // - window['dial']['shortDialogueName'].extraData: additional data to be added to user responses. Is an object (key/value pairings). Overwritten by any user inputs.
   // You can control the dialogue box via the methods:
   // - window['dial']['shortDialogueName'].submit(): submit the data to the server (closing the dialogue box), provided validFunc() returns true
   // - window['dial']['shortDialogueName'].cancel(): cancel the dialogue box

   // Have something happen when the dialogue box is opened
   addEvent(Dialogue.prototype.events, "dialogueOpen", function(ev){
   if(!(ev.id=='theDialogueId')) return; // Check that it is the right dialogue box by checking it has the right id
     // Do something here
     // ev.target and ev.currentTarget both point to the dialogue box object
   });
  }}}

  == How do I add javascript to dialogue box elements? ==
  In order to add javascript events to dialogue elements, simply use the event dialogueOpen (attached to Dialogue.events). Please note that events attached in this way are view-specific NOT dialogue specific. In other words, you will need to attach them in every view where they are relevant, and you will need to add a line at the beginning checking which dialogue is currently open, as in: 
  {{{
  // Example first line of a DialogueOpen listener function
  // It checks whether the dialogue box "dialogues/cld_elements_update_fill.php" is currently open
  // The "_1" at the end of the dialogue box name represents instance number 1 of the dialogue box. Since there is almost never more than one instance simultaneously open, you should always use "_1"
  if(!(ev.id!=='dialogues_cld_elements_update_fill_1')) return;
  }}}

  == 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, and DID click the button with id _submit
  	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;
  }
  }}}
  See also example in section [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#Toinputdataintothetable "To input data into the table"]

  = 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 stop a viewClose event after it has started? ==
  When a person exists a view, a viewClose event is fired. Under some circumstances, you may want to make the closure of the view conditional. Example: If the user has changed something in the view but not yet saved the changes and you therefore want to stop the viewClose event and ask the user if they want to save before exiting.

  In this or similar situations you would need the viewClose event to stop to allow other things to happen such as display a dialogue box. This can be done by setting a value for currentView["_abortViewClose"] in the script which is triggered by the viewClose event. 

  == 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
   * '''selectOuterJoin''': returns the result of an outer join with another table. Below is a code example illustrating it:
  {{{
  #!java
  /*
  * Does an outer join of table restaurants with table joinedTable
  */
  cld_restaurants.addAction('selectOuterJoin');
  var cfg = {}; cfg.params = {}
  cfg.params['joinedTable'] = 'anotherTable';
  cfg.params['onCondition'] = '`restaurants`.`jointField`=`anotherTable`.`jointField`';
  cfg.where = [["`restaurants`.`rank`","=",1],["`restaurants`.`location`","=","Boston"]];//Optional where clause
  cfg.successFunc = function(list){
    logg('Records: ' + dump(list));
  };
  cfg.failureFunc = function(list, out, resp){
  // out: is the object returned, and includes out['replyCode'] and out['replyText']. Available only if there was a response
  // resp: is the text of the http response (xmlhttp.responseText). 
      var msg = '';
      if(out) msg = out['replyCode'] + ': ' + out['replyText']; else msg = 'Http response text is: '+resp;
      logg('Failed: '+msg, 'warn');
  };
  cld_restaurants.act['selectOuterJoin'].reload(cfg);
  }}}
   * '''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!

  See also example in [http://ubiqitus.com:83/trac/Cedrus%20Development%20Environment/wiki/FAQ#HowcanIprocessdialogueboxdatabeforeitissenttotheserver "How can I process dialogue box data before it is sent to the server"]


  == How can I check if the app is online? ==
  Every time a call is made to the server via sendPendingAjax, getData, or reload, localStorage.onlineMode is set to string value "0" (if offline) or "1" (if online). So you can use:
  {{{
  #!java
  // Test whether the app was online last time a call to server was made
  if(localStorage.onlineMode==='0'){

  }

  // Using if(localStorage.onlineMode){} will fail because string '0' evaluates to true in javascript
  }}}

  == 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", "tagName" etc.
      // cfg.displayType = 'csv'; // Allows download of the data in csv form

      // 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)
          // Available for displayType of "table", "self" or "tree" 

          alert(ev.currentTarget.innerHTML);//ev.currentTarget refers to the html row node, so this prints out the row's html
          // console.warn(dump(ev)); // If you feel like exploring
          // console.warn(dump(ev.currentTarget.datum));// Access to the table row data. (Available for graph types "table" and "self"
      };
      // cfg.eventHandler2 and cfg.eventHandler3 can also be defined (except in the case of displayType "tree")

      // 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';
  }}}

  === What are some of the other configuration options available? ===
  The following are available for displayType of "table" and "self":
   * cfg.script: A function to be applied to the display that is being generated. In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.script0: A function to be applied to each data record ''before'' it is displayed. The function receives as argument the data object (of the form {field1:value1, field2:value2, ...}). It then can modify this object in any way that is desired.
  In the case of a table, it is called on each table row, and its argument is the table row. In the case of "self", it is called on each node (which represents one item of data) as it is created. 
   * cfg.className: The class of the row (in the case of tables) or node (in the case of "self")
   * cfg.classNameTD: Class of the <td> elements of a table. Applies to tables only.
   * cfg.oddClass, cfg.evenClass: Classes for odd and even rows of the table, respectively. Applies to tables only.
   * cfg.eventType and cfg.eventHandler: cfg.eventType is an event attached to each row (in the case of tables) and node (in the case of "self"). cfg.eventHandler is the function that is triggered by the event. Also available are a second event (set via cfg.eventType2 and cfg.eventHandler2) and third event (set via cfg.eventType3 and cfg.eventHandler3)

  The following are available for displayType of "tree":
   * cfg.treeBaseTitle: The label of the root of the tree
   * cfg.indexName
   * cfg.eventHandler: Only click events are supported for trees. The listen function is cfg.eventHandler, which takes as argument an event of structure:
     * ev.target and ev.currentTarget: represents the node, the distinction between target and currentTarget being the standard javascript distinction
     * ev.id: the id of the node. For example, the node representing abc/foo/bar will have an id of "abc/foo/bar"
     * ev.treeNode: the tree node (it's an object of class dtree not an html node)

  You can have further control over the output via the returned value:
   * In the case of "table", the html table node is returned
   * In the case of "tree", the html tree node is returned. Also, the dtree object is accessible via the "tree" property of the returned node (so nd.tree accesses the dtree if nd is the returned node).

  == 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
  });

  }}}

  = Exporting/downloading=
  = Can I export/download my app? =
  Of course! Just add "?PHONEGAP=1" at the end of the url of your app, and go to that address. It will allow you to download a zip file containing your app's complete html, javascript and images.
  WARNING: if your app uses cloud/database calls, these are treated by a script on the server, which is not part of the download package.

  = 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.

  = Buttons =
  == How can I insert a button? ==
  Go to the "Insert Shapes, Images, Buttons" menu in Design View. You have three options:
   * Button: Creates a button but does not attach any events to it. You will need to attach your own events to it, for instance using the dialogueButtonPress event.
   * Ok button: When created in a dialogue box, automatically closes the dialogue box and sends the data retrieved to the server
   * Cancel button: When created in a dialogue box, automatically closes the dialogue box, and does nothing further.

  == How can I modify the look and feel of the buttons? ==
  Simply upload two image files of the button you want, one called '_button.png', and one called '_button_pressed.png'. CDE will interpret images with these names as being your intended button design.

  = Keyboard Shortcuts =
  == Are there any keyboard shortcuts? ==
  In the script editor, clicking "Enter" cycles between autocomplete items, and clicking "Space" selects the currently highlighted autocomplete option.

  == Are more shortcuts planned? ==
  Since CDE is designed to be agreeable to use on a tablet, only those shortcuts that would make sense on a tablet will be considered.


name: TicketQuery
author: trac
ipnr: 127.0.0.1
text:
  = !TicketQuery Wiki Macro

  The !TicketQuery macro lets you display information on tickets within wiki pages.
  The query language used by the `[[TicketQuery]]` macro is described in [TracQuery#QueryLanguage TracQuery] page.

  == Usage

  [[MacroList(TicketQuery)]]

  == Example

  ||= **Example** =||= **Result** =||= **Macro** =||
  |-----------------------------------------------------------
  ||=Number of [query:status=new&milestone= Triage tickets]: =||\
  || **[[TicketQuery(status=new&milestone=,count)]]**||\
  || `[[TicketQuery(status=new&milestone=,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of new tickets: =||\
  || **[[TicketQuery(status=new,count)]]**||\
  || `[[TicketQuery(status=new,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of reopened tickets: =||\
  || **[[TicketQuery(status=reopened,count)]]**||\
  || `[[TicketQuery(status=reopened,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of assigned tickets: =||\
  || **[[TicketQuery(status=assigned,count)]]**||\
  || `[[TicketQuery(status=assigned,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of invalid tickets: =||\
  || **[[TicketQuery(status=closed,resolution=invalid,count)]]**||\
  || `[[TicketQuery(status=closed,resolution=invalid,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of worksforme tickets: =||\
  || **[[TicketQuery(status=closed,resolution=worksforme,count)]]**||\
  || `[[TicketQuery(status=closed,resolution=worksforme,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of duplicate tickets: =||\
  || **[[TicketQuery(status=closed,resolution=duplicate,count)]]**||\
  || `[[TicketQuery(status=closed,resolution=duplicate,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of wontfix tickets: =||\
  || **[[TicketQuery(status=closed,resolution=wontfix,count)]]**||\
  || `[[TicketQuery(status=closed,resolution=wontfix,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of fixed tickets: =||\
  || **[[TicketQuery(status=closed,resolution=fixed,count)]]**||\
  || `[[TicketQuery(status=closed,resolution=fixed,count)]]` ||
  |-----------------------------------------------------------
  ||=Total number of tickets: =||\
  || **[[TicketQuery(count)]]**||\
  || `[[TicketQuery(count)]]` ||
  |-----------------------------------------------------------
  ||=Number of tickets reported **or** owned by current user: =||\
  || **[[TicketQuery(reporter=$USER,or,owner=$USER,count)]]**||\
  || `[[TicketQuery(reporter=$USER,or,owner=$USER,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of tickets created this month: =||\
  || **[[TicketQuery(created=thismonth..,count)]]**||\
  || `[[TicketQuery(created=thismonth..,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of closed Firefox tickets: =||\
  || **[[TicketQuery(status=closed,keywords~=firefox,count)]]**||\
  || `[[TicketQuery(status=closed,keywords~=firefox,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of closed Opera tickets: =||\
  || **[[TicketQuery(status=closed,keywords~=opera,count)]]**||\
  || `[[TicketQuery(status=closed,keywords~=opera,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of closed tickets affecting Firefox and Opera: =||\
  || **[[TicketQuery(status=closed,keywords~=firefox opera,count)]]**||\
  || `[[TicketQuery(status=closed,keywords~=firefox opera,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of closed tickets affecting Firefox or Opera: =||\
  || **[[TicketQuery(status=closed,keywords~=firefox|opera,count)]]**||\
  || `[[TicketQuery(status=closed,keywords~=firefox|opera,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of tickets that affect Firefox or are closed and affect Opera: =||\
  || **[[TicketQuery(keywords~=firefox,or,status=closed,keywords~=opera,count)]]**||\
  || `[[TicketQuery(status=closed,keywords~=opera,or,keywords~=firefox,count)]]` ||
  |-----------------------------------------------------------
  ||=Number of closed Firefox tickets that don't affect Opera: =||\
  || **[[TicketQuery(status=closed,keywords~=firefox - opera,count)]]**||\
  || `[[TicketQuery(status=closed,keywords~=firefox -opera,count)]]` ||
  |-----------------------------------------------------------
  ||=Last 3 modified tickets: =||\
  ||**[[TicketQuery(max=3,order=modified,desc=1,compact)]]**||\
  || `[[TicketQuery(max=3,order=modified,desc=1,compact)]]` ||
  |-----------------------------------------------------------
  {{{#!th rowspan=2, style="text-align: left;"
  Details of ticket #1:
  }}}
  {{{#!td style="border-bottom: 0;"
  }}}
  {{{#!td
  `[[TicketQuery(id=1,col=id|owner|reporter,rows=summary,table)]]`
  }}}
  |-
  {{{#!td colspan=2, style="border-top: 0;"
  [[TicketQuery(id=1,col=id|owner|reporter,rows=summary,table)]]
  }}}
  |-----------------------------------------------------------

  == Using the `[[TicketQuery]]` Macro

  The [trac:TicketQuery TicketQuery] macro lets you display lists of tickets matching certain criteria anywhere you can use WikiFormatting.

  Example:
  {{{
  [[TicketQuery(version=0.6|0.7&resolution=duplicate)]]
  }}}

  This is displayed as:
    [[TicketQuery(version=0.6|0.7&resolution=duplicate)]]

  Just like the [wiki:TracQuery#UsingTracLinks query: wiki links], the parameter of this macro expects a query string formatted according to the rules of the simple [wiki:TracQuery#QueryLanguage ticket query language]. This also displays the link and description of a single ticket:
  {{{
  [[TicketQuery(id=123)]]
  }}}

  This is displayed as:
    [[TicketQuery(id=123)]]

  A more compact representation without the ticket summaries is:
  {{{
  [[TicketQuery(version=0.6|0.7&resolution=duplicate, compact)]]
  }}}

  This is displayed as:
    [[TicketQuery(version=0.6|0.7&resolution=duplicate, compact)]]

  If you wish to receive only the number of defects that match the query, use the `count` parameter:
  {{{
  [[TicketQuery(version=0.6|0.7&resolution=duplicate, count)]]
  }}}

  This is displayed as:
    [[TicketQuery(version=0.6|0.7&resolution=duplicate, count)]]

  A graphical use of the macro is with the `format=progress` attribute:
  {{{
  [[TicketQuery(milestone=0.12.8&group=type,format=progress)]]
  }}}

  For example for one of the upcoming milestones, bars are shown by ticket type:
  [[TicketQuery(milestone=0.12.8&group=type,format=progress)]]

  ----
  See also: TracQuery, TracTickets, TracReports, TracGuide


name: TracUpgrade
author: trac
ipnr: 127.0.0.1
text:
  = Upgrade Instructions
  [[TracGuideToc]]
  [[PageOutline(2-4,,inline,unnumbered)]]

  == Instructions

  Typically, there are seven steps involved in upgrading to a newer version of Trac:

  === 1. Bring your server off-line

  It is not a good idea to update a running server: the server processes may have parts of the current packages cached in memory, and updating the code will likely trigger [#ZipImportError internal errors].

  Although a database backup will be implicitly created by default when upgrading the environment, it is always a good idea to perform a full backup of the environment using the [TracBackup hotcopy] command before beginning.

  === 2. Update the Trac Code #UpdatetheTracCode

  Get the new version as described in TracInstall, or through your operating system package manager.

  If you already an earlier version of Trac installed via `easy_install`, it might be easiest to also use `easy_install` to upgrade your Trac installation:

  {{{#!sh
  easy_install --upgrade Trac
  }}}

  You may also want to remove the pre-existing Trac code by deleting the `trac` directory from the Python `lib/site-packages` directory, or remove Trac `.egg` files from former versions.
  The location of the site-packages directory depends on the operating system and the location in which Python was installed. However, the following locations are typical:
   * on Linux: `/usr/lib/python2.X/site-packages`
   * on Windows: `C:\Python2.X\lib\site-packages`
   * on MacOSX: `/Library/Python/2.X/site-packages`

  You may also want to remove the directory in which your static resources are [TracInstall#cgi-bin deployed]. The exact location depends on your platform. This cleanup is not mandatory, but makes it easier to troubleshoot issues later on, as your installation is uncluttered by code or templates from a previous release that is not used anymore. As usual, make a backup before actually removing things.

  === 3. Upgrade the Trac Environment #UpgradetheTracEnvironment

  Environment upgrades are not necessary for minor version releases unless otherwise noted. 

  After restarting, Trac should show the instances which need a manual upgrade via the automated upgrade scripts to ease the pain. These scripts are run via [TracAdmin trac-admin]:
  {{{#!sh
  trac-admin /path/to/projenv upgrade
  }}}

  This command will not have any effect if the environment is already up-to-date.

  Note that a backup of your database will be performed automatically prior to the upgrade. The backup will be saved in the location specified by `[trac]` `backup_dir`.

  === 4. Update the Trac Documentation === #UpdatetheTracDocumentation

  By default, every [TracEnvironment Trac environment] includes a copy of the Trac documentation for the installed version. However, to keep the included documentation in sync with the installed version of Trac, use the following [TracAdmin trac-admin] command to upgrade the documentation:
  {{{#!sh
  trac-admin /path/to/projenv wiki upgrade
  }}}

  Note that this procedure will leave your `WikiStart` page intact.

  === 5. Refresh static resources

  If you have configured your web server to serve static resources directly (accessed using the `/chrome/` URL) then you will need to refresh them using the [TracInstall#MappingStaticResources same command]:
  {{{#!sh
  trac-admin /path/to/env deploy /deploy/path
  }}}

  This will extract static resources and CGI scripts (`trac.wsgi`, etc) from the new Trac version and plugins into `/deploy/path`.

  {{{#!div style="border: 1pt dotted; margin: 1em"
  **Note:** Some web browsers (IE, Opera) cache CSS and Javascript files, so you should instruct your users to manually erase the contents of their browser's cache. A forced refreshed (SHIFT + <F5>) should be enough.
  {{{#!comment
  Remove above note once #9936 is fixed.
  }}}
  }}}

  === 6. Steps specific to a given Trac version

  ==== Upgrading from Trac 1.0 to 1.2 #to1.2

  ===== Python 2.5 no longer supported

  Upgrade Python to at least 2.6 or 2.7, but not 3.0 or greater.

  ===== Obsolete Plugins

  Trac has added functionality equivalent to the following plugins:
  * [https://trac-hacks.org/wiki/AdminEnumListPlugin AdminEnumListPlugin]
  * [https://trac-hacks.org/wiki/DateFieldPlugin DateFieldPlugin]: see the **time** [TracTicketsCustomFields#AvailableFieldTypesandOptions custom field type]
  * [https://trac-hacks.org/wiki/GroupBasedRedirectionPlugin GroupBasedRedirectionPlugin]: the default handler can set as a user preference.
  * [https://trac-hacks.org/wiki/LinenoMacro LinenoMacro]: see WikiProcessors#AvailableProcessors
  * [https://trac-hacks.org/wiki/NeverNotifyUpdaterPlugin NeverNotifyUpdaterPlugin]: see [TracNotification#notification-subscriber-section notification subscribers]
  * [https://trac-hacks.org/wiki/QueryUiAssistPlugin QueryUiAssistPlugin]: see TracQuery#Filters.
  * [https://trac-hacks.org/wiki/TicketCreationStatusPlugin TicketCreationStatusPlugin]: see [#NewWorkflowActions]

  The plugins should be removed when upgrading Trac to 1.2.

  ===== New workflow actions #NewWorkflowActions

  The ticket creation step is controlled with a [TracWorkflow#TicketCreateAction workflow action]. The default workflow has `create` and `create_and_assign` actions. The `create` action will always be added when upgrading the database. The `create_and_assign` action will be added if the workflow has an //assigned// state. You may want to edit your workflow after upgrading the database to customize the actions available on the //New Ticket// page.

  ===== New permissions policy for read-only wiki pages

  Since 1.1.2 the read-only attribute of wiki pages is enabled and enforced only when `ReadonlyWikiPolicy` is in the list of active permission policies. If `[trac] permission_policy` has the default value `DefaultPermissionPolicy, LegacyAttachmentPolicy`, then `ReadonlyWikiPolicy` should be automatically appended to the list when upgrading the environment:
  {{{#!ini
  [trac]
  permission_policies = ReadonlyWikiPolicy,
   DefaultPermissionPolicy,
   LegacyAttachmentPolicy
  }}}

  If other permission policies are enabled, `trac.ini` will need to have `ReadonlyWikiPolicy` appended to the list of active `permission_policies`. See TracFineGrainedPermissions#ReadonlyWikiPolicy for additional details on the proper ordering.

  ==== Upgrading from Trac 0.12 to Trac 1.0 #to1.0

  ===== Python 2.4 no longer supported

  Upgrade Python to at least 2.5, but not 3.0.

  ===== Obsolete Plugins

  Trac has added functionality equivalent to the following plugins:

  * [https://trac-hacks.org/wiki/BatchModifyPlugin BatchModifyPlugin]
  * [https://trac-hacks.org/wiki/GitPlugin GitPlugin]
  * [https://trac-hacks.org/wiki/OverrideEditPlugin OverrideEditPlugin]

  The plugins should be removed when upgrading Trac to 1.0.

  ===== Subversion components not enabled by default for new installations

  The Trac components for Subversion support are no longer enabled by default. To enable the svn support, you need to make sure the `tracopt.versioncontrol.svn` components are enabled, for example by setting the following in the TracIni:
  {{{#!ini
  [components]
  tracopt.versioncontrol.svn.* = enabled
  }}}

  The upgrade procedure should take care of this and change the TracIni appropriately, unless you already had the svn components explicitly disabled.

  ===== Attachments migrated to new location

  Another step in the automatic upgrade will change the way the attachments are stored. Create a backup of the `attachments` directory before upgrading. In case the `attachments` directory contains some files which are //not// attachments, the last step of the migration to the new layout will fail: the deletion of the now unused `attachments` directory can't be done if there are still files and folders in it. You may ignore this error, but better to move them elsewhere and remove the `attachments` directory manually. The attachments themselves are now all located in your environment below the `files/attachments` directory.

  ===== Behavior of `[ticket] default_owner` changed

  Prior to 1.0, the owner field of new tickets always defaulted to `[ticket] default_owner` when the value was not empty. If the value was empty, the owner field defaulted to to the Component's owner. In 1.0 and later, the `default_owner` must be set to `< default >` to make new tickets default to the Component's owner. This change allows the `default_owner` to be set to an empty value if no default owner is desired.

  ==== Upgrading from Trac 0.11 to Trac 0.12

  ===== Python 2.3 no longer supported

  The minimum supported version of Python is now 2.4.

  ===== SQLite v3.x required

  SQLite v2.x is no longer supported. If you still use a Trac database of this format, you'll need to convert it to SQLite v3.x first. See [trac:PySqlite#UpgradingSQLitefrom2.xto3.x] for details.

  ===== [trac:PySqlite] 2 required

  [trac:PySqlite] 1.1.x is no longer supported. Please install 2.5.5 or later if possible, see [#Tracdatabaseupgrade Trac database upgrade] below.

  ===== Obsolete Plugins

  Trac has added functionality equivalent to the following plugins:

  * [https://trac-hacks.org/wiki/AutoQueryPlugin AutoQueryPlugin]
  * [https://trac-hacks.org/wiki/AdminConsoleProviderPatch AdminConsoleProviderPatch]
  * [https://trac-hacks.org/wiki/AnchorMacro AnchorMacro]: see WikiFormatting#SettingAnchors
  * [https://trac-hacks.org/wiki/TicketChangePlugin TicketChangePlugin]: see [TracPermissions#TicketSystem TICKET_EDIT_COMMENT permission]
  * [https://trac-hacks.org/wiki/TicketDeletePlugin TicketDeletePlugin]: see `tracopt.ticket.deleter`
  * [https://trac-hacks.org/wiki/SubversionLocationPlugin SubversionLocationPlugin]: see TracRepositoryAdmin#Repositories
  * [https://trac-hacks.org/wiki/WikiCreoleRendererPlugin WikiCreoleRendererPlugin]: see [trac:WikiCreole]
  * [https://trac-hacks.org/wiki/RepoRevisionSyntaxPlugin RepoRevisionSyntaxPlugin] (added in 0.12.1)

  The plugins should be removed when upgrading Trac to 0.12.

  ===== Multiple Repository Support

  The latest version includes support for multiple repositories. If you plan to add more repositories to your Trac instance, please refer to TracRepositoryAdmin#Migration.

  This may be of interest to users with only one repository, since there is now a way to avoid the potentially costly resync check at every request.

  ===== Resynchronize the Trac Environment Against the Source Code Repository

  Each [TracEnvironment Trac environment] must be resynchronized against the source code repository in order to avoid errors such as "[trac:#6120 No changeset ??? in the repository]" while browsing the source through the Trac interface:

  {{{#!sh
  trac-admin /path/to/projenv repository resync '*'
  }}}

  ===== Improved repository synchronization

  In addition to supporting multiple repositories, there is now a more efficient method for synchronizing Trac and your repositories.

  While you can keep the same synchronization as in 0.11 adding the post-commit hook as outlined in TracRepositoryAdmin#Synchronization and TracRepositoryAdmin#ExplicitSync will allow more efficient synchronization and is more or less required for multiple repositories.

  Note that if you were using the `trac-post-commit-hook`, ''you're strongly advised to upgrade it'' to the new hook documented in the above references and [TracWorkflow#Howtocombinethetracopt.ticket.commit_updaterwiththetestingworkflow here], as the old hook will not work with anything else than the default repository and even for this case, it won't trigger the appropriate notifications.

  ===== Authz permission checking

  The authz permission checking has been migrated to a fine-grained permission policy. If you use authz permissions (aka `[trac] authz_file` and `authz_module_name`), you must add `AuthzSourcePolicy` in front of your permission policies in `[trac] permission_policies`. You must also remove `BROWSER_VIEW`, `CHANGESET_VIEW`, `FILE_VIEW` and `LOG_VIEW` from your global permissions with `trac-admin $ENV permission remove` or the "Permissions" admin panel.

  ===== Microsecond timestamps

  All timestamps in database tables, except the `session` table, have been changed from "seconds since epoch" to "microseconds since epoch" values. This change should be transparent to most users, except for custom reports. If any of your reports use date/time columns in calculations (e.g. to pass them to `datetime()`), you must divide the values retrieved from the database by 1'000'000. Similarly, if a report provides a calculated value to be displayed as a date/time (i.e. with a column named "time", "datetime", "changetime", "date", "created" or "modified"), you must provide a microsecond timestamp, that is, multiply your previous calculation with 1'000'000.

  ==== Upgrading from Trac 0.10 to Trac 0.11

  ===== Site Templates and Styles

  The templating engine has changed in 0.11 to Genshi, please look at TracInterfaceCustomization for more information.

  If you are using custom CSS or modified templates in the `templates` directory of the TracEnvironment, you will need to convert them to the Genshi way of doing things. To continue to use your style sheet, follow the instructions at TracInterfaceCustomization#SiteAppearance.

  ===== Trac Macros, Plugins

  The Trac macros will need to be adapted, as the old-style wiki-macros are not supported anymore due to the drop of [trac:ClearSilver] and the HDF. They need to be converted to the new-style macros, see WikiMacros. When they are converted to the new style, they need to be placed into the plugins directory instead and not wiki-macros, which is no longer scanned for macros or plugins.

  ===== For FCGI/WSGI/CGI users

  For those who run Trac under the CGI environment, run this command in order to obtain the trac.*gi file:
  {{{#!sh
  trac-admin /path/to/env deploy /deploy/directory/path
  }}}

  This will create a deploy directory with the following two subdirectories: `cgi-bin` and `htdocs`. Then update your Apache configuration file `httpd.conf` with this new `trac.cgi` location and `htdocs` location.

  ===== Web Admin plugin integrated

  If you had the [trac:WebAdmin] plugin installed, you can uninstall it as it is part of the Trac code base since 0.11.

  ===== New Default Configurable Workflow

  When you run `trac-admin <env> upgrade`, your `trac.ini` will be modified to include a `[ticket-workflow]` section. The workflow configured in this case is the original workflow, so that ticket actions will behave like they did in 0.10:

  {{{#!Workflow width=500 height=240
  leave = * -> *
  leave.operations = leave_status
  leave.default = 1
  accept = new -> assigned
  accept.permissions = TICKET_MODIFY
  accept.operations = set_owner_to_self
  resolve = new,assigned,reopened -> closed
  resolve.permissions = TICKET_MODIFY
  resolve.operations = set_resolution
  reassign = new,assigned,reopened -> new
  reassign.permissions = TICKET_MODIFY
  reassign.operations = set_owner
  reopen = closed -> reopened
  reopen.permissions = TICKET_CREATE
  reopen.operations = del_resolution
  }}}

  There are some significant caveats in this, such as accepting a ticket sets it to 'assigned' state, and assigning a ticket sets it to 'new' state. So you will probably want to migrate to "basic" workflow; [trac:source:trunk/contrib/workflow/migrate_original_to_basic.py contrib/workflow/migrate_original_to_basic.py] may be helpful. See TracWorkflow for a detailed description of the new basic workflow.

  === 7. Restart the Web Server #RestarttheWebServer

  If you are not running [wiki:TracCgi CGI], reload the new Trac code by restarting your web server.

  == Known Issues

  === Customized Templates

  Trac supports customization of its Genshi templates by placing copies of the templates in the `<env>/templates` folder of your [TracEnvironment environment] or in a common location specified in the [[TracIni#GlobalConfiguration| [inherit] templates_dir]] configuration setting. If you choose to do so, be aware that you will need to repeat your changes manually on a copy of the new templates when you upgrade to a new release of Trac (even a minor one), as the templates will likely evolve. So keep a diff around.

  The preferred way to perform TracInterfaceCustomization is to write a custom plugin doing an appropriate `ITemplateStreamFilter` transformation, as this is more robust in case of changes: we usually won't modify element `id`s or change CSS `class`es, and if we have to do so, this will be documented in the [trac:TracDev/ApiChanges] pages.

  === !ZipImportError

  Due to internal caching of zipped packages, whenever the content of the packages change on disk, the in-memory zip index will no longer match and you'll get irrecoverable !ZipImportError errors. Better anticipate and bring your server down for maintenance before upgrading.
  See [trac:#7014] for details.

  === Wiki Upgrade

  `trac-admin` will not delete or remove default wiki pages that were present in a previous version but are no longer in the new version.

  === Trac database upgrade

  A known issue in some versions of [trac:PySqlite] (2.5.2-2.5.4) prevents the trac-admin upgrade script from successfully upgrading the database format. It is advised to use either a newer or older version of the sqlite python bindings to avoid this error. For more details see ticket [trac:#9434].

  === Parent dir

  If you use a Trac parent env configuration and one of the plugins in one child does not work, none of the children will work.

  == Related topics

  === Upgrading Python

  Upgrading Python to a newer version will require reinstallation of Python packages: Trac itself of course, but also [http://pypi.python.org/pypi/setuptools easy_install], if you've been using that. If you are using Subversion, you'll also need to upgrade the Python bindings for svn.

  ==== Windows and Python 2.6

  If you've been using !CollabNet's Subversion package, you may need to uninstall that in favor of [http://alagazam.net/ Alagazam], which has the Python bindings readily available, see [trac:TracSubversion]. That package works without tweaking.

  === Changing Database Backend

  The [https://trac-hacks.org/wiki/TracMigratePlugin TracMigratePlugin] on [https://trac-hacks.org trac-hacks.org] has been written to assist in migrating between SQLite, MySQL and PostgreSQL databases.

  === Upgrading from older versions of Trac #OlderVersions

  For upgrades from versions older than Trac 0.10, refer first to [trac:wiki:0.10/TracUpgrade#SpecificVersions].

  -----
  See also: TracGuide, TracInstall


name: TracNotification
author: trac
ipnr: 127.0.0.1
text:
  = Email Notification of Ticket Changes
  [[TracGuideToc]]

  Trac supports notification of ticket changes via email. 

  Email notification is useful to keep users up-to-date on tickets of interest, and also provides a convenient way to post all ticket changes to a dedicated mailing list.

  Disabled by default, notification can be activated and configured in [TracIni trac.ini].

  == Receiving Notification Mails
  When reporting a new ticket or adding a comment, enter a valid email address or your Trac username in the ''reporter'', ''assigned to/owner'' or ''cc'' field. Trac will automatically send you an email when changes are made to the ticket, depending on how notification is configured.

  === How to use your username to receive notification mails

  To receive notification mails, you can either enter a full email address or your Trac username. To get notified with a simple username or login, you need to specify a valid email address in your [/prefs preferences]. 

  Alternatively, a default domain name ('''`smtp_default_domain`''') can be set in the TracIni file, see [#ConfigurationOptions Configuration Options] below. In this case, the default domain will be appended to the username, which can be useful for an "Intranet" kind of installation.

  When using apache and mod_kerb for authentication against Kerberos / Active Directory, usernames take the form ('''`username@EXAMPLE.LOCAL`'''). To avoid this being interpreted as an email address, add the Kerberos domain to  ('''`ignore_domains`''').

  === Ticket attachment notifications

  Since 1.0.3 Trac will send notifications when a ticket attachment is added or deleted. Usually attachment notifications will be enabled in an environment by default. To disable the attachment notifications for an environment the `TicketAttachmentNotifier` component must be disabled:
  {{{#!ini
  [components]
  trac.ticket.notification.TicketAttachmentNotifier = disabled
  }}}

  == Configuring SMTP Notification

  '''Important:''' The [[TracIni#trac-base_url-option|[trac] base_url]] option must be configured for links in the notification message to be correctly generated. 

  === Configuration Options
  These are the available options for the `[notification]` section in trac.ini:

  [[TracIni(section=notification)]]

  === Example Configuration (SMTP)
  {{{#!ini
  [notification]
  smtp_enabled = true
  smtp_server = mail.example.com
  smtp_from = notifier@example.com
  smtp_replyto = myproj@projects.example.com
  smtp_always_cc = ticketmaster@example.com, theboss+myproj@example.com
  }}}

  === Example Configuration (`sendmail`)
  {{{#!ini
  [notification]
  smtp_enabled = true
  email_sender = SendmailEmailSender
  sendmail_path = /usr/sbin/sendmail
  smtp_from = notifier@example.com
  smtp_replyto = myproj@projects.example.com
  smtp_always_cc = ticketmaster@example.com, theboss+myproj@example.com
  }}}

  === Subscriber Configuration
  The default subscriptions are configured in the [TracIni#notification-subscriber-section "[notification-subscriber]"] section.

  [[TracIni(section=notification-subscriber)]]

  Each user can override these defaults in their ''Notifications'' preferences.

  For example to unsubscribe from notifications for one's own changes and comments, the rule "Never notify: I update a ticket" should be added above other subscription rules.

  The subscription rule name on the left side of the `=` can be anything, it has no meaning outside this configuration file. The subscriber name on the right side of the `=` must be one of the subscribers listed in the above table.

  The following attributes of default subscriptions can be configured:
  * `.distributor` (Default: `email`)
    * Other values require plugins. For example `on-site` requires th:OnSiteNotificationsPlugin.
  * `.priority` (Default: `100`)
    * Smaller values override larger values.
    * If you use `0`, then users will not be able to override this rule.
  * `.adverb` (Default: `always`)
    * `never` can be used to silence other subscription rules with higher values.
  * `.format` (Default: `text/plain`)
    * Other values require plugins. For example `text/html` requires th:TracHtmlNotificationPlugin.

  === Example Configuration (default subscriptions)
  {{{#!ini
  [notification-subscriber]
  always_notify_owner = TicketOwnerSubscriber
  always_notify_owner.distributor = email
  always_notify_owner.priority = 100
  always_notify_owner.adverb = always
  always_notify_owner.format = text/plain

  always_notify_previous_updater = TicketPreviousUpdatersSubscriber

  never_notify_updater = TicketUpdaterSubscriber
  never_notify_updater.adverb = never
  never_notify_updater.priority = 0

  notify_cc_html = CarbonCopySubscriber
  notify_cc_html.format = text/html
  }}}

  === Customizing the e-mail subject
  The e-mail subject can be customized with the `ticket_subject_template` option, which contains a [http://genshi.edgewall.org/wiki/Documentation/text-templates.html Genshi text template] snippet. The default value is:
  {{{#!genshi
  ${prefix} #${ticket.id}: ${summary}
  }}}
  The following variables are available in the template:

   * `env`: The project environment object (see [trac:source:/trunk/trac/env.py env.py]).
   * `prefix`: The prefix defined in `smtp_subject_prefix`.
   * `summary`: The ticket summary, with the old value if the summary was edited.
   * `ticket`: The ticket model object (see [trac:source:/trunk/trac/ticket/model.py model.py]). Individual ticket fields can be accessed by appending the field name separated by a dot, eg `${ticket.milestone}`.

  === Customizing the e-mail content

  The notification e-mail content is generated based on `ticket_notify_email.txt` in `trac/ticket/templates`. You can add your own version of this template by adding a `ticket_notify_email.txt` to the templates directory of your environment. The default is:

  {{{#!genshi
  ${ticket_body_hdr}
  ${ticket_props}
  # if ticket.new:
  ${ticket.description}
  # else:
  #   if changes_body:
  ${_('Changes (by %(author)s):', author=change.author)}

  ${changes_body}
  #   endif
  #   if changes_descr:
  #     if not changes_body and not change.comment and change.author:
  ${_('Description changed by %(author)s:', author=change.author)}
  #     endif
  ${changes_descr}
  --
  #   endif
  #   if change.comment:

  ${_('Comment:') if changes_body else
    _('Comment (by %(author)s):', author=change.author)}

  ${change.comment}
  #   endif
  # endif
  --
  ${_('Ticket URL: <%(link)s>', link=ticket.link)}
  ${project.name} <${project.url or abs_href()}>
  ${project.descr}
  }}}

  See the [trac:CookBook/Notification/Templates cookbook] for additional template customization recipes.

  == Sample Email
  {{{
  #42: testing
  ---------------------------+------------------------------------------------
         Id:  42             |      Status:  assigned                
  Component:  report system  |    Modified:  Fri Apr  9 00:04:31 2004
   Severity:  major          |   Milestone:  0.9                     
   Priority:  lowest         |     Version:  0.6                     
      Owner:  anonymous      |    Reporter:  jonas@example.com               
  ---------------------------+------------------------------------------------
  Changes:
    * component:  changeset view => search system
    * priority:  low => highest
    * owner:  jonas => anonymous
    * cc:  daniel@example.com =>
           daniel@example.com, jonas@example.com
    * status:  new => assigned

  Comment:
  I'm interested too!

  --
  Ticket URL: <http://example.com/trac/ticket/42>
  My Project <http://myproj.example.com/>
  }}}

  == Using GMail as the SMTP relay host

  Use the following configuration snippet:
  {{{#!ini
  [notification]
  smtp_enabled = true
  use_tls = true
  mime_encoding = base64
  smtp_server = smtp.gmail.com
  smtp_port = 587
  smtp_user = user
  smtp_password = password
  }}}

  where ''user'' and ''password'' match an existing GMail account, ie the ones you use to log in on [http://gmail.com].

  Alternatively, you can use `smtp_port = 25`.[[br]]
  You should not use `smtp_port = 465`. Doing so may deadlock your ticket submission. Port 465 is reserved for the SMTPS protocol, which is not supported by Trac. See [trac:comment:2:ticket:7107 #7107] for details.

  == Troubleshooting

  If notifications are not working, inspect the [TracLogging log] for error messages.

  Notification errors are not always reported through the web interface, so the user who submits a change or creates a ticket may not get notified about a notification failure. The Trac administrator needs to look at the log to find the error message and traceback.

  === ''Permission denied'' error

  Typical error message:
  {{{#!sh
    ...
    File ".../smtplib.py", line 303, in connect
      raise socket.error, msg
    error: (13, 'Permission denied')
  }}}

  This error usually comes from a security settings on the server: many Linux distributions do not allow the web server (Apache, ...) to post email messages to the local SMTP server.

  Many users get confused when their manual attempts to contact the SMTP server succeed:
  {{{#!sh
  telnet localhost 25
  }}}
  This is because a regular user may connect to the SMTP server, but the web server cannot:
  {{{#!sh
  sudo -u www-data telnet localhost 25
  }}}

  In such a case, you need to configure your server so that the web server is authorized to post to the SMTP server. The actual settings depend on your Linux distribution and current security policy. You may find help in the Trac [trac:MailingList MailingList] archive.

  Relevant ML threads:
   * SELinux: http://article.gmane.org/gmane.comp.version-control.subversion.trac.general/7518

  For SELinux in Fedora 10:
  {{{#!sh
  $ setsebool -P httpd_can_sendmail 1
  }}}

  === ''Suspected spam'' error

  Some SMTP servers may reject the notification email sent by Trac.

  The default Trac configuration uses Base64 encoding to send emails to the recipients. The whole body of the email is encoded, which sometimes trigger ''false positive'' spam detection on sensitive email servers. In such an event, change the default encoding to "quoted-printable" using the `mime_encoding` option.

  Quoted printable encoding works better with languages that use one of the Latin charsets. For Asian charsets, stick with the Base64 encoding.

  ----
  See also: TracTickets, TracIni, TracGuide, [trac:TracDev/NotificationApi]


