tag:blogger.com,1999:blog-4280338379511248992024-03-14T02:52:05.961-07:00Xataface TipsAnonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.comBlogger15125tag:blogger.com,1999:blog-428033837951124899.post-70099196901876757502014-06-25T16:09:00.002-07:002014-06-25T16:16:05.858-07:00Improve Performance of Calculated Fields with Dynamic TablesThe next version (2.1) of <a href="http://xataface.com/" target="_blank">Xataface</a> includes a new simple, but powerful, feature that can drastically improve performance on tables that include grafted fields - especially when those grafted fields include expensive calculations. The feature is called "dynamic tables".<br />
<br />
A dynamic table is a MySQL table that is generated with the results of a query. It is dependent on the tables included in the query, so that if any of them are modified, the dynamic table will be deleted and rebuilt. They are very similar to MySQL views, except that they can perform much better in environments where updates are rare relative to reads.<br />
<br />
Let's motivate dynamic tables by way of an example. Consider a database with a table of contacts, called "current_call_list". There is also a "calls" table to record all phone calls made to each contact.<br />
The current_call_list itself only contains data pertaining to the contact (e.g. name, phone, email, etc...), but we would like the list view to also include some calculated information such as how many calls have been made to the contact, when the last call was made, what some recent call comments were, and what some of their responses were to questions asked during the calls.<br />
<br />
Xataface makes it relatively easy to add these fields as grafted fields using the __sql__ directive of the fields.ini file (for the current_call_list table). Our first attempt is as follows:<br />
<br />
<script src="https://gist.github.com/shannah/0149380f4ff5615589ca.js"></script><br />
<br />
This works. But it has brought with it some hefty performance baggage that we feel in a big way as the database grows beyond a few thousand records.<br />
<br />
The problem is that all of the calculated fields require subqueries and aggregate calculations to be performed on the calls table.<br />
<br />
<h3>
Using a MySQL View</h3>
<br />
One solution is to create a view that produces this information, and then use that view instead to obtain our grafted fields. We could define the view as follows:<br />
<br />
<script src="https://gist.github.com/shannah/ed4cbab82f38bc6ae35b.js"></script><br />
<br />
Then we could change the __sql__ directive in our fields.ini file to:<br />
<br />
<script src="https://gist.github.com/shannah/4a3f2113949730f9f6d8.js"></script><br />
<br />
Notice that our SQL query has become much more efficient. Rather than performing aggregate calculations on the calls table, it does a simple left join on our new view. Generally joins on key fields in MySQL are *very* fast. In this case, we're cheating a bit by using a view, so we will likely lose a little performance there.<br />
<br />
Playing around with the database a little bit more, shows that the performance is marginally better, but still sluggish on larger databases. It appears that the view still has to perform some intense calculations each time. <br />
<br />
<h3>
Using a Dynamic Table</h3>
<br />
In our application, the calls table is updated relatively infrequently compared to the number of read requests. We should be able to take advantage of this fact to cache our calculations whenever the calls table is changed. This is precisely what a Dynamic table is for. The Dynamic table generates itself as necessary with the results of an SQL query. In each HTTP request, it checks the modification of all dependent tables to see if they have changed since it was last generated. If they have changed, then the dynamic table is cleared out and repopulated.<br />
<br />
Let's modify our example to use a dynamic table instead of a mysql view:<br />
<br />
<script src="https://gist.github.com/shannah/a0a738874c4f08df0bba.js"></script><br />
<br />
In this example, we make use of the xf\db\DynamicTable class. The constructor takes 3 parameters:<br />
<br />
<ol>
<li>The name of the table to create.</li>
<li>An array of SQL statements that should be executed to create the table and populate it. The first statement is usually a CREATE TABLE statement. Subsequent statements are the queries that populate the table.</li>
<li>An array of table names that this table depends on. If any of these tables are changed, the dynamic table should be deleted and regenerated.</li>
</ol>
<div>
The update() method is where the actual work is performed.</div>
<div>
<br /></div>
<div>
<h4>
Where do I place this code?</h4>
</div>
<div>
<br /></div>
<div>
The best place to for this code is somewhere that will be executed in every request, and before the query is sent to the database. Xataface 2.1 has supports the beforeLoadResultSet() exactly for this purpose. It is executed just before the result set is loaded, so it gives us a chance to generate the table before it is used.</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/8fbae9926102176c2939.js"></script></div>
<div>
<br /></div>
<div>
Since we have named this table the same as the View in our last example (note: we need to drop the view before doing this), we don't need to make any changes to the __sql__ directive in our fields.ini file.</div>
<div>
<br /></div>
<div>
Since the SQL query that Xataface has to perform is just a simple join between two *real* tables, the application is much more responsive. Actually an order of magnitude faster.</div>
<br />
<div>
<br /></div>
<br />
<br />Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com189tag:blogger.com,1999:blog-428033837951124899.post-46965026130724869542014-06-06T13:10:00.001-07:002014-06-06T13:13:04.292-07:00Getting Started: Most Common MistakesThis article is dedicated to future new users of Xataface who have difficulty getting their first app up and running. I have created a number of "getting started" tutorials for Xataface, and have posted a couple of getting started videos. Some of these include:<br />
<br />
<ul>
<li><a href="http://xataface.com/documentation/tutorial/getting_started/first_application" target="_blank">Creating Your First Application</a> (from the Getting Started tutorial) - Shows use of the "makesite" script as well as manual installation steps.</li>
<li><a href="http://xataface.com/wiki/How_to_build_a_PHP_MySQL_Application_with_4_lines_of_code" target="_blank">How to build a PHP/MySQL Application with 4 Lines of Code</a> (Shows the "manual" approach).</li>
<li><a href="http://xataface.com/wiki/about#toc5" target="_blank">About Xataface > Your First App</a> - Also shows the "manual" approach.</li>
<li><a href="http://www.xataface.com/assets/dataface/about/presentations/Introduction_to_Dataface.pdf" target="_blank">Building Data-Centric Websites with Dataface</a> - (Very old powerpoint presentation)</li>
<li><a href="http://www.xataface.com/videos#blog-under-4.flv" target="_blank">Build a Blog in 4 Minutes </a>(Video) - Shows use of Web-based install tool.</li>
<li><a href="http://www.xataface.com/videos#introduction.flv" target="_blank">Introduction to Xataface</a> (Video) - Also shows use of Web-based install tool.</li>
</ul>
<div>
These tutorials demonstrate how to create your first Xataface application using one of three methods:</div>
<div>
<ol>
<li>Using the makesite shell script.</li>
<li>Using the web-based install tool.</li>
<li>Creating the application manually.</li>
</ol>
<div>
All three of these approaches *should* work, but I personally favour the manual approach for the following reasons:</div>
</div>
<div>
<br /></div>
<div>
<ol>
<li>It is really easy. 2 lines of PHP, 5 or 6 lines in the conf.ini file, and copy the .htaccess file. and you're done.</li>
<li>It helps you to understand the application structure from the beginning. You're going to have to start customizing your app by hand sooner or later, so why now make it sooner.</li>
</ol>
<h3>
Common Mistakes</h3>
</div>
<h4>
The Web-based Installer Cannot Connect to the Database</h4>
<div>
If the web-based installer can't connect to the database, it is likely that your database is located on a different domain than localhost. The Xataface installer script has the database host hard-coded into the script as "localhost". If your database is located on a different server, as it may be on some shared hosting environments, you may need to modify the "<a href="https://github.com/shannah/xataface/blob/master/installer.php#L33" target="_blank">installer.php</a>" file and change the DB_HOST constant to the correct value for your server.</div>
<div>
<br /></div>
<h4>
The Web-based Installer Keeps Prompting for Username and Password</h4>
<div>
The web installer uses the password that you provide to connect to the database. It must be a valid mysql username and password that will have access to at least the database on which you want to create your app. You may also enter your MySQL root password here - as it will only be used to create the app - you will have another opportunity to specify the user account that the app will use for connecting to the database. If it keeps prompting you for the password that means that you are entering incorrect credentials. If you're sure that you're entering the correct information here, I wouldn't spend too much time trying to solve it here. Just skip straight to a <a href="http://xataface.com/wiki/How_to_build_a_PHP_MySQL_Application_with_4_lines_of_code" target="_blank">manual installation</a>.</div>
<div>
<br /></div>
<h4>
Xataface Says "Installed Correctly" but I can't access my app</h4>
<div>
Likely you are following the <a href="http://www.xataface.com/documentation/tutorial/getting_started" target="_blank">Getting Started Tutorial</a>, and you have just reached the end of the <a href="http://www.xataface.com/documentation/tutorial/getting_started/installation" target="_blank">"Xataface Installation" section</a>. Installing Xataface is actually easy, it just involves copying it to your web server. However, xataface being installed is different than your app being installed. You still need to proceed to the next section of the getting started tutorial on creating your first app. Better yet, just follow the <a href="http://xataface.com/wiki/How_to_build_a_PHP_MySQL_Application_with_4_lines_of_code" target="_blank">manual installation instructions</a> so you don't have to futz with any app creation scripts.</div>
<div>
<br /></div>
<h4>
Blank White Screen</h4>
<div>
The single most common "help" request I get on the forum is the blank white screen issue. If you get a blank white screen when you try to access your app, it means that there is an error that caused PHP execution to fail. Likely it is an issue with a require statement pointing to the wrong path, but, technically it could be *anything*. <b>You need to check your PHP error log to see what the actual error is</b>. This is a good opportunity to get acquainted with your PHP error log because it will be your best friend going forward. If you post to the forum without knowing where your error log is or where errors are being written, you'll likely just be sent on a mission to find it. <a href="http://xataface.com/wiki/Troubleshooting#toc1" target="_blank">Some tips on solving the "blank white screen issue".</a></div>
<div>
<br /></div>
<h4>
No Such File or Directory "xataface"</h4>
<div>
The following error is common for first time users:</div>
<div>
<br /></div>
<div>
<b>Warning</b>: require_once(../xataface-git/public-api.php): failed to open stream: No such file or directory in <b>/Applications/XAMPP/xamppfiles/htdocs/index.php</b> on line <b>5</b><br />
<br />
<b>Fatal error</b>: require_once(): Failed opening required '../xataface-git/public-api.php' (include_path='.:/Applications/XAMPP/xamppfiles/lib/php') in<b>/Applications/XAMPP/xamppfiles/htdocs/index.php</b> on line <b>5</b></div>
<div>
<b><br /></b></div>
This error will occur if the require_once statement at the beginning of your index.php file is pointing to the wrong place, or if the place it is pointing is not readable by the web server process. The first line of your index.php file is likely something like:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;"><?php<br />require_once 'xataface/public-api.php';</span><br />
<br />
This should point to your xataface/public-api.php file. Make sure that it points to the correct place, and that the permissions on the file allow reading from the web server process.<br />
<br />
<h4>
Application Loads But All images and styles are missing</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3yZMkwtmWEVwhmJzREsFiyOXDSkSx6Mk0nMhMrLuszLvpeWByZSJfIK0P5L6aRxW9NX9NY86UbXYsZSgdS1m1oQWA4ETOLgid48KSy928W1-UK3Ayi5DdFKI6AqmyIJ3XuSvYIpysz3-k/s1600/Screen+Shot+2014-06-06+at+1.10.27+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3yZMkwtmWEVwhmJzREsFiyOXDSkSx6Mk0nMhMrLuszLvpeWByZSJfIK0P5L6aRxW9NX9NY86UbXYsZSgdS1m1oQWA4ETOLgid48KSy928W1-UK3Ayi5DdFKI6AqmyIJ3XuSvYIpysz3-k/s1600/Screen+Shot+2014-06-06+at+1.10.27+PM.png" height="366" width="400" /></a></div>
<br />
If your application loads but it looks weird (e.g. missing images, no style, broken page formatting, etc...), it means that the 2nd parameter of your df_init() function (inside your index.php file) is pointing to the wrong place.<br />
<br />
In your index.php file, you'll have a line like:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">df_init(__FILE__, 'xataface')->display();</span><br />
<br />
The 2nd parameter (in this case 'xataface') should be the web-accessible URL to the xataface directory. It may be an absolute URL (e.g. http://example.com/path/to/xataface), or a relative URL (e.g. 'xataface'). This parameter is used as a base for loading all images and CSS stylesheets in the Xataface directory. If it is wrong, you'll get no style. After you fix this, you should see the page formatting correctly.Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com132tag:blogger.com,1999:blog-428033837951124899.post-83309481795716681712014-06-04T12:53:00.000-07:002014-06-04T12:53:30.490-07:00Using mysqli in Xataface<a href="http://xataface.com/" target="_blank">Xataface</a> now supports the new <a href="http://www.php.net/manual/en/book.mysqli.php" target="_blank">mysqli drivers</a>. Support has been added on <a href="https://github.com/shannah/xataface" target="_blank">GitHub</a> and it will be incorporated into the next release. For now (for upgrade compatibility), it will still use the <a href="http://www.php.net/manual/en/book.mysql.php" target="_blank">default mysql driver</a>, but you can explicitly tell it to use mysqli by adding the following to the <span style="font-family: Courier New, Courier, monospace;">[_database] </span>section of your application's <span style="font-family: Courier New, Courier, monospace;">conf.ini</span> file:<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">driver=mysqli</span><br />
<br />
E.g.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">[_database]<br /> host=localhost</span><br />
<span style="font-family: Courier New, Courier, monospace;"> user=steve</span><br />
<span style="font-family: Courier New, Courier, monospace;"> password=mypass</span><br />
<span style="font-family: Courier New, Courier, monospace;"> name=dbname</span><br />
<span style="font-family: Courier New, Courier, monospace;"> driver=mysqli</span><br />
<br />
<h3>
New Database Compatibility Layer</h3>
<br />
Adding support for mysqli whilst maintaining backward compatibility with the <a href="http://www.php.net/manual/en/book.mysql.php" target="_blank">mysql driver</a> was achieved by adding a new compatibility layer. If you look through the <a href="https://github.com/shannah/xataface/commit/64ad7b26de8c94349d0b9d5647b43547f4d3931a" target="_blank">changes to the Xataface source</a>, you'll notice that all of the calls to <span style="font-family: Courier New, Courier, monospace;">mysql_xxx </span>have been changed to <span style="font-family: Courier New, Courier, monospace;">xf_db_xxx</span>. E.g.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">mysql_connect()</span> was changed to <span style="font-family: Courier New, Courier, monospace;">xf_db_connect()</span>, <span style="font-family: Courier New, Courier, monospace;">mysql_query()</span> changed to <span style="font-family: Courier New, Courier, monospace;">xf_db_query()</span>, etc..<br />
<br />
These new functions are defined inside the <a href="https://github.com/shannah/xataface/tree/master/xf/db/drivers" target="_blank">xf/db/drivers directory</a>. Xataface loads the correct driver at the beginning of each request.<br />
<br />
<h3>
Automatic Conversion Tool</h3>
<br />
This change was made via <a href="https://github.com/shannah/xataface/blob/master/tools/fix-deprecated-mysql-ant-task.xml" target="_blank">this ANT build script</a> that is now a part of the Xataface distribution. It scours the entire source base looking for instances of the old <span style="font-family: Courier New, Courier, monospace;">mysql_xxx()</span> functions, and it replaces them with the corresponding <span style="font-family: Courier New, Courier, monospace;">xf_db_xxx()</span> function.<br />
<br />
<b>Example usage:</b><br />
<br />
<span style="font-family: Courier New, Courier, monospace;">ant -f fix-deprecated-mysql-ant-task.xml</span><br />
<br />
This will scour the current directory and all subdirectories. If you want to run it on a particular base directory, you can use the -<span style="font-family: Courier New, Courier, monospace;">Dbasedir=PATH</span> parameter. E.g.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">ant -Dbasedir=/Users/matt/files/myapp -f fix-deprecated-mysql-ant-task.xml</span><br />
<br />
If you are using Xataface in an existing application that makes <span style="font-family: Courier New, Courier, monospace;">mysql_xxx()</span> calls at any point, and you want to use the mysqli driver, then you'll need to update your application accordingly, as you cannot use both <span style="font-family: Courier New, Courier, monospace;">mysqli_xxx()</span> functions and <span style="font-family: Courier New, Courier, monospace;">mysql_xxx() </span>functions on the same database connection.<br />
<br />
I'll be going through the existing Xataface modules in the next while to port them over to the new compatibility layer, but if you run into issues with a module before I get to it, you should be able to just run the ANT task on the module's directory and it will be updated automatically.Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com174tag:blogger.com,1999:blog-428033837951124899.post-39892586481677977792014-04-22T12:19:00.003-07:002014-04-22T12:41:17.510-07:00Adding More Interactivity to Your Forms<blockquote class="tr_bq">
How to use Xataface, AJAX, and the export_json action to dynamically load data into your forms fields</blockquote>
<br />
The Xataface new record and edit record forms provide you with an easy (code-free) way to insert and update records in your database. The <a href="http://xataface.com/wiki/fields.ini_file">fields.ini file </a>enables you to customize which fields should be editable, and which widgets should be used to edit them. In some cases you may find that you need to add more interactivity to the form than is provided out of the box. One of the most common requests, for example, is to make a set of select widgets whose options are dependent on one another (e.g. If you have a "country" and "province" field, you want to automatically filter the options in the "province" select list to only show those provinces in the selected country). I ultimately developed the <a href="http://xataface.com/dox/modules/depselect/latest/">depselect widget</a> to address this use case, but there are still edge cases where the depselect might not be a perfect fit.<br />
<br />
<h3>
The Problem</h3>
Another request I sometimes get is to enable the user to load data into some fields of the form that is dependent of some partial data is has already been filled in. Take, for example, a university job posting application where departments advertise their TA and Sessional Instructor positions each semester. Every semester, the department administrator would add a new record to their "job postings" table that includes instructions for applicants, deadlines, etc... In order to make the department administrator's job a little bit easier, they requested to have a button added to the form that would copy the posting instructions from a previous job posting into the current form's instructions field. Then they could just make a few small changes to the instructions rather than starting from scratch each semester (or having to manually copy and paste from a previous record).<br />
<br />
One option that is *very* easy to implement in Xataface is provide a <a href="http://xataface.com/wiki/fieldname__default">default value</a> for the instructions field. This would almost solve the problem, but not completely. Default values are loaded when form is generated. But we may not know which instructions to "copy" into our field until the user has entered some data into the form (e.g. the department, semester, job category, etc...). We really needed to add some dynamic behaviour to the form to allow the instructions to be populated after the form is generated, but before it is submitted. Xataface provides the tools to achieve this without great difficulty also.<br />
<br />
<h3>
The Solution</h3>
<ol>
<li>Use the before_instructions_widget block (<a href="http://xataface.com/documentation/tutorial/getting_started/changing-look-and-feel">read more about blocks and slots</a>) to insert a button into the form.</li>
<li>Add a javascript "click" handler to our button that takes some of the values entered into the form, makes an AJAX request to retrieve appropriate instructions, and replace the contents of the "instructions" field with the instructions that were retrieved.</li>
</ol>
<div>
<h4>
Adding the Button</h4>
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/11189267.js"></script></div>
<div>
<br /></div>
<div>
Reloading the form for the <span style="font-family: Courier New, Courier, monospace;">job_postings</span> table reveals that the button has been added.</div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDWrQfi4yenRlVQ1zlvSDntqwsGA3QdFwbE9ehzSzf45HVgZH2R2ZJJp1V0d7acN-vsuXYTJ_r4KRJr8ka_QTYuXBCFNniYL1K8mJN3G0BI8QGKbSMDcfwPokqVmxaaaqyFiby1VOAyE8g/s1600/Screen+Shot+2014-04-22+at+11.59.37+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDWrQfi4yenRlVQ1zlvSDntqwsGA3QdFwbE9ehzSzf45HVgZH2R2ZJJp1V0d7acN-vsuXYTJ_r4KRJr8ka_QTYuXBCFNniYL1K8mJN3G0BI8QGKbSMDcfwPokqVmxaaaqyFiby1VOAyE8g/s1600/Screen+Shot+2014-04-22+at+11.59.37+AM.png" height="257" width="400" /></a></div>
</div>
<div>
<br /></div>
<div>
Notice, on line 2 of the above snippet, that we use the <a href="https://rawgit.com/shannah/xataface/master/doc_output/html/class_dataface___javascript_tool.html">Dataface_JavascriptTool class</a> (<a href="http://sjhannah.com/blog/?p=195">read more about JavascriptTool here</a>) to include a custom javascript file that we will create next.</div>
<div>
<br /></div>
<div>
<h4>
The Custom Javascript</h4>
</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/11189403.js"></script><br />
<br />
There's a fair bit of code here, so let's go through it line by line.<br />
<br />
Lines 1 to 3 show our "require" statements. These are a special feature of the Dataface_Javascript tool to be able to use libraries that are included in Xataface, installed modules, and the application. Here we require jQuery (because it just makes everything easier), the <a href="https://rawgit.com/shannah/xataface/master/doc_output/jsdoc/symbols/XataJax.form.html">xatajax.form.core.js</a> library (which provides some convenience functions for interacting with Xataface forms), and <a href="https://github.com/shannah/xataface-module-ckeditor/blob/master/js/ckeditor.js">ckeditor.js</a> (because the instructions field in this app happens to use the <a href="https://github.com/shannah/xataface-module-ckeditor">CKeditor widget</a> and we need to use its API for setting the data in that field properly).<br />
<br />
Line 4 is just the opening of our anonymous Javascript function which is used to provide a local scope to our Javascript. This is best practice so that you don't pollute the global namespace. This function is closed and executed on line 66.<br />
<br />
Lines 5 and 6 is where we make local references to some global objects and functions. Xataface's version of jQuery doesn't assign the jQuery object to the "$" alias by default. This is to maintain compatibility with other libraries that may use this alias. To make our lives easier, we assign a reference to the jQuery object that is only accessible to our local scope. <br />
<br />
The <a href="https://rawgit.com/shannah/xataface/master/doc_output/jsdoc/symbols/XataJax.form.html#.findField">Xataface.form.findField</a> function (line 6) is a convenient Xataface function for finding the HTML fields on a given form that correspond with fields in the database. <br />
<br />
Line 8 shows another level of wrapping and scoping inside the registerXatafaceDecorator() function. This function takes a function as a parameter which will be executed when the body is finished loading in the browser. It is very similar to jQuery's $(document).ready() method except that it is called when a document fragment is loaded by AJAX as well as when the document is initially loaded in the browser. Notice that the callback function that is passed to registerXatafaceDecorator takes a single "node" parameter. This represents the root node of the document fragment that was loaded. This allows you to target your document decoration to only include the fragment that was loaded - rather than the entire document.<br />
<br />
On line 9, we obtain a reference to the button that we added in the before_instructions_widget block. <br />
<br />
On Lines 10-14, we obtain references to the fields in the form that we will need for building our AJAX request. Notice that we use the <a href="https://rawgit.com/shannah/xataface/master/doc_output/jsdoc/symbols/XataJax.form.html#.findField">findField()</a> function (we made a reference to this on line 6) to obtain these fields. In this case we make references to the semester_id, job_type, and department_id fields because we may need to pass these values as part of our AJAX request to determine which instructions we want to load. E.g. If the job posting is for the "CMPT" department, then it wouldn't make sense to load previous instructions for the MATH department.<br />
<br />
On line 16, we attach a click handler to the import button. This handler is what will be called when the user clicks on the button to load instructions. These is where we build the AJAX request to load the previous instructions.</div>
<div>
<br />
Lines 17-27 show the basic GET parameters that I plan to pass to as part of the AJAX request. Let's take a look at these parameters and discuss what they do.<br />
<br />
The -action parameter (line 18) shows that we intend to pass this request to the built-in Xataface action "export_json", which will return the found set as a JSON data structure. This is a very useful action for serving AJAX requests. In some cases you may still need to create your own custom actions to return the data that your AJAX request requires, but in most cases this built-in action should do just fine. When using the export_json action, you can just follow standard <a href="http://xataface.com/wiki/URL_Conventions">Xataface URL conventions</a> for specifying which records to include in the result set. E.g. here we specify that we want to fetch records from the "job_postings" table (line 19) , we only want one record (line 20), and the results should be sorted by semester id in descending order (line 22) -- effectively this means we want only the most recent match. <br />
<br />
The export_json action also takes some additional parameters. For a full list, and some documentation, you can check out the <a href="https://github.com/shannah/xataface/blob/master/actions/export_json.php">action's source itself</a>. In this example, we set "-mode" to "browse" (line 21) so that the instructions won't be truncated. By default export_json uses list mode which only returns the first 255 characters of text fields. Also, since we only need the "instructions" of the job posting, we use the "--fields" directive to declare that we only need this one field. We could have listed multiple fields here separated by spaces.<br />
<br />
On lines 23-25 we initialize query values for the department_id, job_type, and semester_id fields. This is optional and only added for readability. Passing a blank value for a query field (following <a href="http://xataface.com/wiki/URL_Conventions">Xataface URL conventions</a>) doesn't affect the query at all. We set these query parameters to more specific values based on the current form data on lines 29-44.<br />
<br />
On lines 29-31, we check to see if the semester_id field is filled in. If it is, we add a filter to the query to only return job_postings in semesters earlier than the current one. Notice the use of the '<' less than prefix in the query value to indicate a search for semesters less than the given semester.<br />
<br />
Lines 32-27, we check if the department id field has been filled in. If so, we add it to the filter parameters of the query. If not, we throw an error .<br />
<br />
Lines 39-44 show similar handling for the job type field.<br />
<br />
Finally Lines 46-56 show the actual AJAX request. The response will be an array of objects, where each object contains an 'instructions' property. The following is a screenshot from the Chrome network inspector to show what the response will look like.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM4m5hGGEUT5O0XR8EAJjMs22RfEe1TzN_OyJ-XPJNxCARkGHHd9Avz4LHIq60hnspHTrIvzW_yKw3dQQT2qKO3INMDCpAV4i4M7Q11LVr3jm1_S1kadxD0hafZhtgZyKLylDfpvUa0GWk/s1600/Screen+Shot+2014-04-22+at+11.56.04+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhM4m5hGGEUT5O0XR8EAJjMs22RfEe1TzN_OyJ-XPJNxCARkGHHd9Avz4LHIq60hnspHTrIvzW_yKw3dQQT2qKO3INMDCpAV4i4M7Q11LVr3jm1_S1kadxD0hafZhtgZyKLylDfpvUa0GWk/s1600/Screen+Shot+2014-04-22+at+11.56.04+AM.png" height="51" width="320" /></a></div>
<br />
<br />
In this handler we first check that the response exists and that its length is greater than 0 (line 47). On line 48 we use the <a href="http://docs.cksource.com/CKEditor_3.x/Users_Guide">CKEDITOR api </a>to <a href="http://ckeditor.com/forums/CKEditor-3.x/Getting-CKEDITOR-instance">get the CKeditor instance</a> for the 'instructions' field. If it can be found, we set the data using the CKeditor setData() method.<br />
<br />
Now, if we load the form, enter the required fields, and press the button, it should load the instructions field with the data that we want.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjCkpQNxPYzyGALVGPNrFUtzyLTGDoY4gr9QDU2stwOeSn1fD6DT-TVNxYRWALG4g7UR7T2Rq98TCMpMw2zzHltgPr7pDHvMmXiRYd5HG1UCyT_UfxBTTLQV3R8oCD5iGTXStsIUmzdgx2/s1600/Screen+Shot+2014-04-22+at+12.01.28+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjCkpQNxPYzyGALVGPNrFUtzyLTGDoY4gr9QDU2stwOeSn1fD6DT-TVNxYRWALG4g7UR7T2Rq98TCMpMw2zzHltgPr7pDHvMmXiRYd5HG1UCyT_UfxBTTLQV3R8oCD5iGTXStsIUmzdgx2/s1600/Screen+Shot+2014-04-22+at+12.01.28+PM.png" height="260" width="400" /></a></div>
<br />
<br /></div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com43tag:blogger.com,1999:blog-428033837951124899.post-18300233563247264212014-04-15T11:10:00.000-07:002014-04-15T11:38:29.409-07:00Editable Select Widget in List View<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_FPakyHam0dy7ArSlA5oohlZZUK351dWaaP55kceQQJnX-EiqyRaU9YlDf892AwT1aNic8Etd0Ti-42x8nj8lMku9cLKOIIOOQ7RzWktuthBs-jX__uijpv-nORmlwVSfT7k9hKCBhxkz/s1600/Screen+Shot+2014-04-15+at+11.23.57+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_FPakyHam0dy7ArSlA5oohlZZUK351dWaaP55kceQQJnX-EiqyRaU9YlDf892AwT1aNic8Etd0Ti-42x8nj8lMku9cLKOIIOOQ7RzWktuthBs-jX__uijpv-nORmlwVSfT7k9hKCBhxkz/s1600/Screen+Shot+2014-04-15+at+11.23.57+AM.png" height="153" width="400" /></a></div>
<br />
<br />
The default Xataface list view is a read-only table that shows the records of the current table. Occasionally I get requests to make some of the columns editable by way of a drop-down box. For example, suppose we have built an issue tracker in Xataface. The list page for the "issues" table likely has columns like "title", "status", "assignee", etc.. It might be helpful, in this case, if we could change the status of an issue directly in list view without having to go to the edit form (or the update form). One solution is to change the "status" column to include a drop-down select widget rather than just a read-only string. This is actually quite easy to do, in Xataface, even though it doesn't have this type of functionality built-in.<br />
<div>
<br /></div>
<h3>
The Solution</h3>
<div>
Our solution has three parts:</div>
<div>
<ol>
<li>Implement the status__renderCell() method in the issues table delegate class to display the <select> list.</li>
<li>Set the noLinkFromListView flag in the fields.ini file definition for the "status" field.</li>
<li>Implement a Javascript handler to save changes via AJAX when the select list value is changed.</li>
</ol>
<h4>
Custom cell content with status__renderCell()</h4>
</div>
<div>
The status__renderCell() method should return an string containing HTML. Xataface looks for this method to populate the content of the "status" method in list view.</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10751829.js"></script></div>
<div>
<br /></div>
<div>
In this snippet we have hard-coded the possible status values. But it would be better to use a Xataface valuelist for these values. I leave this for you to do as an exercise. Notice that I added a "data-record-id" attribute to the <select> tag. We will use this later when we add our Javascript functionality.</div>
<div>
<br /></div>
<div>
If you reload the interface, you should now see a select list appear in the "status" column of each row. Unfortunately when you click on it, to try to change the value, you will be taken to the details page for that row. This is because the <select> list is rendered inside of an <a> tag, which Xataface generates for all cells in list view (to make them clickable). You can disable this by setting the noLinkFromListView flag in the fields.ini file</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10751912.js"></script></div>
<div>
<br /></div>
<div>
Now, if you reload the page, you should be able to select different options in your select list without the page changing on you. It doesn't actually "work" yet, but at least it looks right. The next step is to add functionality to the drop-down lists.</div>
<div>
<br /></div>
<div>
<h4>
Adding Save Functionality</h4>
</div>
<div>
<br /></div>
<div>
In order to add save functionality, we will need to step into the world of Javascript. Xataface provides a rich set of tools for developing javascript functionality. Our first order of business is to create an empty Javascript file and have it included in our application at the appropriate time. </div>
<div>
If your application doesn't already have a directory named "js" in its root, create it now. </div>
<div>
<br /></div>
<div>
Note: It is important that the directory is named "js" because this is where the Dataface_JavascriptTool is preset to look for scripts. </div>
<div>
<br /></div>
<div>
Try adding a script with a simple "alert()" message, then we'll be able to tell whether the script is included properly in our App.</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10752043.js"></script></div>
<div>
<br /></div>
<div>
<h4>
Including the Javascript File In Our App</h4>
</div>
<div>
<br /></div>
<div>
To include the js file in our app, let's modify the earlier snippet we created to display the select list:</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10752987.js"></script></div>
<div>
<br /></div>
<div>
Notice that we added a call to Dataface_JavascriptTool::import() to import the script we created. You might be concerned that this piece of code is executed several times per page, and thus many copies of our script will be included in the final HTML page. No need to worry, though, as the Dataface_Javascript tool import() method checks for duplicates to make sure that the script is only included once.</div>
<div>
<br /></div>
<div>
Before proceeding, you should now try to reload the list view if your app to make sure that the "alert()" message fires. If it does not, that means that something is wrong so you'll need to back track to figure out why the file is not being included.</div>
<div>
<br /></div>
<div>
If the alert() message fired properly, then we can move on to actually implement our javascript. I always begin by creating a closure to wrap all of the javascript code. This way my variables won't bleed into the global namespace.</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10753125.js"></script></div>
<div>
<br /></div>
<div>
<h4>
Importing Javascript Libraries</h4>
</div>
<div>
Another nice thing about the <span style="font-family: Courier New, Courier, monospace;"><a href="https://rawgit.com/shannah/xataface/master/doc_output/html/class_dataface___javascript_tool.html">Dataface_Javascript</a></span> tool is that it provides linking/compiling functionality to be able to include other javascript libraries in a similar manner to the way you would import them in server-side languages like C, Java, Ruby, and Python. The syntax is easy. For our purposes here, we'll be using the jQuery library (which is included with Xataface), and the <a href="https://rawgit.com/shannah/xataface/master/doc_output/jsdoc/symbols/xataface.IO.html">xataface.IO</a> library, which provides convenience methods for reading and writing database records over AJAX. Therefore, I'll place the following require statements at the beginning of my file.</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10753466.js"></script></div>
<div>
<br /></div>
<div>
These "require" statements instruct Xataface to search in the javascript include paths for files named "<span style="font-family: Courier New, Courier, monospace;">jquery.packed.js</span>" and "<span style="font-family: Courier New, Courier, monospace;">xataface/IO.js</span>" and include the contents of the first match for each of them. The search paths, by default are <span style="font-family: Courier New, Courier, monospace;">{yourapp}/js</span>, <span style="font-family: Courier New, Courier, monospace;">{xataface}/js</span> but this can be augmented.</div>
<div>
<br /></div>
<div>
Now for the actual code:</div>
<div>
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10753904.js"></script></div>
<div>
<br /></div>
<div>
If you're not familiar with jQuery, this code may look daunting. Have no fear, jQuery is actually quite easy to understand once you have done a 10 minute tutorial. In this case we are making use of our "<span style="font-family: Courier New, Courier, monospace;">status-drop-down</span>" class that we added to the <span style="font-family: Courier New, Courier, monospace;"><select></span> tag in our status__renderCell() method to identify all of the drop-downs. We then attach a listener to each select's "change" event.</div>
<div>
<br /></div>
<div>
We use the xataface.IO.update() method to actually update the the record. The update() method expects a Xataface record ID as the first parameter. This is a unique string that defines a record. It is composed in the form <span style="font-family: Courier New, Courier, monospace;">{tablename}?key1=val1&key2=val2...&keyn=valn</span>. We don't really need to worry about how it is formed here, because we embedded the key as the <select> tag's data-record-id attribute in the <span style="font-family: Courier New, Courier, monospace;">status__renderCell() </span>method so that we can just access it here.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com41tag:blogger.com,1999:blog-428033837951124899.post-10915383745273473152014-04-14T15:00:00.001-07:002014-04-14T15:00:23.107-07:00Detecting When Values Are ChangedXataface provides <a href="http://www.xataface.com/documentation/tutorial/getting_started/triggers" target="_blank">hooks</a> that allow you to respond to many events in the workflow of your application. The most fundamental hooks involve saving records. These include:<br />
<br />
<ul>
<li><a href="http://xataface.com/wiki/beforeSave" target="_blank">beforeSave</a> - called just before a record is saved to the database.</li>
<li><a href="https://rawgit.com/shannah/xataface/master/doc_output/html/group__delegate_class.html#ga0411a88e01e6c3a79c4a828e90b5f5e8" target="_blank">afterSave</a> - called just after a record is saved to the database.</li>
<li>beforeInsert - called just before a record is inserted into the database.</li>
<li>afterInsert - called just after a record is inserted into the database.</li>
<li>beforeUpdate - called just before an existing record is updated in the database.</li>
<li>afterUpdate - called just after an existing record is updated in the database.</li>
</ul>
<div>
Notice that before/afterSave is called every time a record is saved (i.e. for both updates and inserts). One common requirement is to perform an action only if a particular value was changed in the record. For example, suppose you have a "status" field in your table, and if this field is set to "broken", an email should be sent to the site administrator to "fix" whatever is broken. A first attempt at solving this problem might be:</div>
<div>
<script src="https://gist.github.com/shannah/10683980.js"></script>
</div>
<div>
This will send an email to the admin (assume that ADMIN_EMAIL is a constant defined elsewhere) every time a record is saved and the 'status' is set to 'broken'. But there is a major problem with this solution. This will send an email *every* time the record is saved. So if the record was set to broken, then a user went in to make additional changes, it would send another email after each save. Ideally we want to just send the email when the value of 'status' first changed to 'broken'.<br />
<h3>
Detecting Field Value Changes</h3>
</div>
<div>
Dataface_Record has a <a href="https://rawgit.com/shannah/xataface/master/doc_output/html/group__delegate_class.html#ga0411a88e01e6c3a79c4a828e90b5f5e8" target="_blank">valueChanged()</a> method that makes it easy to detect if a field has been modified since the record was last loaded. With this knowledge, you might try to modify your afterSave() method as follows:</div>
<div>
<script src="https://gist.github.com/shannah/10684243.js"></script>
</div>
<div>
Unfortunately, if you try to run this code, you'll find that the mail is now *never* sent. This is because the "changed" flags on Dataface_Record objects are reset before any of the afterXXX() methods are called. If you want to detect a change, you need to do so in the beforeXXX() methods of the save cycle. E.g.
</div>
<div>
<script src="https://gist.github.com/shannah/10684336.js"></script>
</div>
<div>
Now if you try out the application it should work the way that you expect. If the value of the 'status' field is changed, an email will be sent as expected. But this still isn't ideal because the email is technically sent before the "save" cycle is finished. So there is a possibility that a field validator causes the save to be canceled or fail. That may seem like a slim possibility, but it is better to be sure.
</div>
<div>
So, our ideal solution is to use the afterSave() hook, but to only send the email if the status field has changed.
<br />
<h3>
Passing Data from beforeSave to afterSave Callback</h3>
</div>
<div>
The solution, then, is to use the beforeSave() handler to detect the change, and then somehow tell the afterSave() callback to perform the update in the case that a change is found. There are many ways to pass data between these methods, but my favourite solution is to use the "pouch" of the Dataface_Record object.</div>
<div>
The "pouch" is just an associative array that is a member of a Dataface_Record object. There you can store arbitrary data to be passed around and between contexts of your application. In the following example, we use the beforeSave() handler to detect the change in the 'status' field. We then register a flag in the record's pouch which will be detected in the afterSave() method.</div>
<div>
<script src="https://gist.github.com/shannah/10684903.js"></script>
</div>
<div>
This version works exactly as required. Notice that after detecting the 'send_broken_email' flag in the afterSave() method, it clears this flag so that it doesn't affect future cycles, in case the same record object is saved multiple times during the same HTTP request.
<br />
<br />
<h3>
Using PHP Anonymous Functions</h3>
<br />
The previous version works fine, but as a matter of style, I prefer to use PHP's anonymous functions (available since 5.3) to define callbacks in the beforeSave() handler which can be called in the afterSave() handler.<br />
<br /></div>
<div>
<script src="https://gist.github.com/shannah/10685428.js"></script>
</div>
<div>
The thing I like about this style is that all of the business logic will then reside in the beforeSave() method. If I create additional rules that require some action in the afterSave() method, I can just add more callbacks. The afterSave() method doesn't need to change.. it just faithfully executes the list of handlers. This could even be extended to add callbacks from other areas of your app. But I'll leave that for another post.
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com57tag:blogger.com,1999:blog-428033837951124899.post-64187409507754124192014-04-12T09:38:00.000-07:002014-04-12T09:38:06.572-07:00Context-Specific CSS RulesOne of the most frequent questions on the Xataface forum is how to hide parts of the UI in a context-specific way. E.g. You might want to hide the search box only for a specific table, or for a specific class of user, or only when logged in. As with most things in Xataface, there are many ways to achieve this, but one of the most flexible methods is using CSS.<br />
<br />
If you haven't already added a custom CSS file to your application, you can do so by way of the <a href="https://rawgithub.com/shannah/xataface/master/doc_output/html/class_dataface___application.html#a8922a305e2a3e1cc73a2505046c375b1" target="_blank">Dataface_Application::addHeadContent()</a> method. Usually I place this inside my <a href="http://xataface.com/wiki/Application_Delegate_Class" target="_blank">application delegate class'</a> <a href="http://xataface.com/wiki/beforeHandleRequest" target="_blank">beforeHandleRequest()</a> method. E.g.<br />
<br />
<script src="https://gist.github.com/shannah/10543224.js"></script>
For this example, you would create a file named <em>style.css</em> and place it in the root directory of your application. Because the beforeHandleRequest() method is called at the beginning of every request, this will result in the style.css stylesheet being included in every page of the site. Now, if we wanted to hide the search box on the top of the UI (assuming we're using the g2 theme), we could add the following rule to our CSS file:<br />
<script src="https://gist.github.com/shannah/10543489.js"></script>
<br />
(This is because the "id" attribute of the search form is "top-search-form". See the template source <a href="https://github.com/shannah/xataface-module-g2/blob/master/templates/Dataface_Main_Template.html#L124" target="_blank">here</a>).<br />
<br />
This solution hides the search box throughout the entire site. But what if you want to only hide it for certain tables. E.g. I recently received a question on the forum about hiding the search box only in the "dashboard" table. Two possible solutions for this come to mind:<br />
<br />
<ol>
<li>Create a separate stylesheet for the dashboard table and only include it if the request is for the dashboard table.<br />
<script src="https://gist.github.com/shannah/10543939.js"></script>
<br />
</li>
<li>
Add CSS classes to the HTML <body> tag conditionally, and then use these to add CSS rules that only apply for desired context:<br />
<script src="https://gist.github.com/shannah/10544261.js"></script>
<br />
This solution makes use of the block__body_atts to add a class attribute to the <body> tag and adds a CSS class of the form "table-{tablename}" to the body tag which can be targeted from the CSS file. In the case of the dashboard table, the <body> tag will have a "table-dashboard" class.
</li>
</ol>
<div>
I personally prefer the second approach because it is much more flexible, and we can keep all of our styles in a single stylesheet. For example, if we wanted to hide the search form for both the dashboard and foobar tables, we would just change our rule to:
<br />
<script src="https://gist.github.com/shannah/10544491.js"></script>
</div>
<div>
This strategy can be expanded to mark more than just table contexts. For example, you could also add classes to mark whether the user is logged in or the role of the user. Then you could create CSS rules that only apply to a particular role of user or whether or not the user is logged in.</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com85tag:blogger.com,1999:blog-428033837951124899.post-51864284081046727422013-09-10T21:07:00.000-07:002013-09-10T21:12:27.258-07:00Excel-Friendly CSV FilesXataface has supported CSV export since the beginning, but I have recently been made aware that the CSV files that Xataface produces haven't been "MS Excel" friendly when it comes to UTF-8 special characters. You may have noticed that if you export a data set as CSV from Xataface where some records include special characters (like é and å), and try to open it with MS Excel, the characters become garbled. These CSV files work fine with Open Office, Google Docs, and pretty much every other spreadsheet program out there. It's just that MS Excel doesn't seem to support UTF-8 encoded CSV files.<br />
<br />
(Note: You can still import them into Excel using its import features, where you can specify the encoding explicitly, but if you try to simply open the CSV file with Excel, it will assume that the encoding is Mac Roman or Windows 1252 depending on whether you're on Mac or Windows).<br />
<br />
Many solutions were tried to try to make these CSV files work nicely with Excel, but it seems that Excel simply can't handle UTF-8!<br />
<br />
Today I implemented an experimental feature in Xataface to be able to export data to CSV that Excel can handle. It works by using UTF-16 encoding instead of UTF-8 as well as some other settings specifically designed to cater to Excel (e.g. tab delimiter instead of commas, and a different mimetype).<br />
<br />
You can enable this feature by adding the following to your application's conf.ini file:<br />
<br />
<code></code><br />
<pre><code>[export_csv]
format=excel
</code></pre>
<br />
<div>
Then you can just use the export CSV action as normal (e.g. from list view click "Export" > "CSV" if using the g2 theme, or click the "Export CSV" icon in the upper right of the list view if using the default theme).<br />
<br />
This change is currently in the master repository on <a href="https://github.com/shannah/xataface" target="_blank">GitHub</a> and will be included in the 2.0.3 release.</div>
<div>
<br /></div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com12tag:blogger.com,1999:blog-428033837951124899.post-38581051887632365162013-09-06T13:32:00.001-07:002013-09-06T14:09:48.953-07:00Making Adding/Removing Related Records Easier<iframe allowfullscreen="" frameborder="0" height="450" src="//www.youtube.com/embed/m71oyLK8x6k" width="600"></iframe>
<br />
Xataface has supported 1:n (i.e. one-to-many) and n:m (i.e. many-to-many) relationships from the beginning. By default, when a relationship is defined, it causes a corresponding tab to appear in the UI for each record of the originating table. E.g. If I define a relationship named "Courses" on the `Students` table that represents all of the courses for which a student is registered (i.e. a 1:n relationship from the `Students` table to the `Courses` table), it causes a "Courses" tab to appear inside the details view of a Student record.<br />
<br />
The relationship tab will include a list of all records in the relationship as well as buttons to add or remove related records.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimHh_qotSYt2PPhXNoxy-sNVkCglSroSY0MLLqWsqZdxAplLQZFPMO1nmTBfytjDsRtDJMBxH_VX8pt-xcgNJw8VkSKVsMd4p3yk77LNqK2ZeKRuIKPnOzhiXzwmlUe47782vMxW1dcUoM/s1600/Screen+Shot+2013-09-06+at+12.41.43+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimHh_qotSYt2PPhXNoxy-sNVkCglSroSY0MLLqWsqZdxAplLQZFPMO1nmTBfytjDsRtDJMBxH_VX8pt-xcgNJw8VkSKVsMd4p3yk77LNqK2ZeKRuIKPnOzhiXzwmlUe47782vMxW1dcUoM/s640/Screen+Shot+2013-09-06+at+12.41.43+PM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The "Courses" tab showing the records in the courses relationship for a particular student.</td></tr>
</tbody></table>
<br />
<a href="https://github.com/shannah/xftips-tutorial-easier-relationships" target="_blank">View the source for this example application on GitHub</a><br />
<h2>
Editing Relationships from the Edit/New Record Form</h2>
Adding related records one at a time using the "Add New Related Record" form can be cumbersome, especially in cases where you routinely need to add multiple records to the relationship for every record in the originating table. Xataface provides the ability to add and remove related records directly on the "edit" and "new" forms of the originating table. In cases where the size of the relationship is small, this makes a lot of sense, as it puts all of the "editing" functionality in one place for the user.<br />
<br />
The following widget types can be used to edit relationships:<br />
<br />
<table>
<thead>
<tr>
<th>Widget Type</th>
<th>Supports 1:n</th>
<th>Supports n:m</th>
</tr>
</thead>
<tbody>
<tr>
<td>grid</td><td>Yes</td><td>Yes</td>
</tr>
<tr>
<td>checkbox</td><td>No</td><td>Yes</td>
</tr>
<tr>
<td>tagger</td><td>No</td><td>Yes</td>
</tr>
</tbody>
</table>
<br />
<h3>
Using the grid Widget</h3>
<div>
The grid widget allows you to add, remove, and edit related records in a grid with one row per record. The configuration happens in the originating table of the relationship, inside its fields.ini file. The fields.ini definition a grid widget looks like:<br />
<br /></div>
<div>
<code></code><br />
<pre><code>[Courses]
transient=1
widget:type=grid
relationship=Courses
widget:columns="subject,course_number,course_title"
</code></pre>
</div>
<div>
<div>
<br /></div>
<div>
<br />
The key elements of this definition are:<br />
<br />
<ul>
<li>The "transient" directive tells Xataface that this field is not backed by an actual column in the Students table. (It is sort of a way of saying to XF "No, I'm not loopy! I realize that there is no such column, but I want this anyways!)</li>
<li>The "relationship" directive specifies the relationship that should be used to populate the grid. It happens to be the same as the column name we've chosen for the transient field, but it doesn't necessarily have to. We could have named this field anything we like.</li>
<li>The "widget:columns" directive is a comma-delimited list of the columns in the related table that should appear in the grid.</li>
</ul>
<blockquote class="tr_bq">
(Please note that the example here using a 1:n relationship from students to courses is somewhat unrealistic - it assumes that the course records are not entities themselves, but just value records that are entirely owned by a student. In the real world you would probably have a much more complex table structure - and most certainly would be using a n:m relationship between students and courses).</blockquote>
<div>
<br /></div>
<div>
After adding this configuration, let's visit the "Edit" form of a Student record. It should display our grid widget as follows:</div>
<div>
<br /></div>
<div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXE_xylqQtKVVkN9WVv9ZTZhwopraXnNxO3aGCN2nZ7Zv26LHz-fKt6ZYHk3G0CBFYLT3cwdbJmjJ10aq6kRKrAFu20_dH1JDDUPXbIByb8oSqWCMoeRMOfMGYVlGWzHeJFDvfe2L1R11n/s1600/Screen+Shot+2013-09-06+at+12.39.13+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="338" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiXE_xylqQtKVVkN9WVv9ZTZhwopraXnNxO3aGCN2nZ7Zv26LHz-fKt6ZYHk3G0CBFYLT3cwdbJmjJ10aq6kRKrAFu20_dH1JDDUPXbIByb8oSqWCMoeRMOfMGYVlGWzHeJFDvfe2L1R11n/s640/Screen+Shot+2013-09-06+at+12.39.13+PM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Using the grid widget to edit the Courses relationship. In this case the courses relationship is a 1:n relationship so it doesn't allow you to add existing records.</td></tr>
</tbody></table>
<br /></div>
<div>
<br /></div>
<div>
When you enter a value into any field of the last row, it will create a new empty row at the end of the widget for you to add a new record. In this way, you can add as many related records as you like.</div>
<div>
<br />
<a href="https://github.com/shannah/xftips-tutorial-easier-relationships/tree/grid_widget_1_to_n" target="_blank">View the source for this example on Github</a></div>
<div>
<h3>
Using Checkboxes</h3>
</div>
<div>
If you are using a n:m relationship and you want to provide a simple way for the user to indicate which records from table A are related to which records in table B, you can appropriate the checkbox widget to generate a simple checkbox grid from which the user can select the related records. The configuration for this might look as follows (assuming that the "Courses" relationship is now an n:m relationship):</div>
<div>
<br /></div>
<code></code><br />
<pre><code>[Courses]
transient=1
widget:type=checkbox
relationship=Courses
</code></pre>
<br />
<div>
<br /></div>
Notice that this definition uses the same key elements as the grid widget, but it doesn't require a "widget:columns" directive because it is only displaying the "title" of each related record - no need to specify columns.<br />
<br />
After adding this configuration, we can visit the "Edit" form for a Student record, and it should display our widget as follows:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRUP5BRw1iPQhP2BmkReruDInpfSAXVNhKlCclU5xv3vR1X0TfLH8FEUtARC77c9jkDhoDajUfuYHxW8uCYRq4zI49KudPZpT8ZLndzMJOMzVEDe7rBQAdraKYeihc95oSKeK39iHocyYn/s1600/Screen+Shot+2013-09-06+at+12.21.54+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="391" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRUP5BRw1iPQhP2BmkReruDInpfSAXVNhKlCclU5xv3vR1X0TfLH8FEUtARC77c9jkDhoDajUfuYHxW8uCYRq4zI49KudPZpT8ZLndzMJOMzVEDe7rBQAdraKYeihc95oSKeK39iHocyYn/s640/Screen+Shot+2013-09-06+at+12.21.54+PM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Using the checkbox widget to edit the courses in the Student's courses relationship.</td></tr>
</tbody></table>
<br />
Now you can specify which courses a user is registered in by simply checking the courses' checkboxes on the Student edit form.<br />
<br />
<a href="https://github.com/shannah/xftips-tutorial-easier-relationships/tree/checkbox_widget" target="_blank">View the source for this example on GitHub</a><br />
<h3>
Using the Tagger Widget</h3>
The tagger widget can also be used to manage n:m relationships. It provides an intuitive "Tagging" interface for the user. To use it, you will need to install the tagger module. For more information about the tagger widget see <a href="http://xataface-tips.blogspot.ca/2013/05/using-xataface-tagger-widget-for-nm.html">this post</a>.<br />
<br />
<h2>
Caveats</h2>
This article glossed over a few items that you will likely face if you set up your relationships to be edited by a widget. Some of these include:<br />
<br />
<ul>
<li>Transient widgets are displayed as one of the first fields on the form by default. You may want to adjust the field order to move it to a more appropriate place.</li>
<li>Now that you are providing a way for users to add and remove related records on the edit form, you may want to disable the standard way of adding records in the related tab.</li>
<li>For some relationships, it is sufficient to just have it displayed in the edit form. You may want to hide the default relationship tab altogether, or to move the related list inside the "View" tab as a section.</li>
</ul>
<div>
You can find information on most of these issues in the <a href="http://xataface.com/forum">Xataface forum</a> and <a href="http://xataface.com/wiki">wiki</a>. And I hope to write more articles on these in the future so stay tuned.</div>
<br />
<h3>
Source Code</h3>
All of the source code for this article is available on Github. The repository includes 4 branches:<br />
<br />
<ol>
<li><a href="https://github.com/shannah/xftips-tutorial-easier-relationships" target="_blank">Master</a> - The default application with a single 1:n relationship from Students to Courses with standard relationship management.</li>
<li><a href="https://github.com/shannah/xftips-tutorial-easier-relationships/tree/grid_widget_1_to_n" target="_blank">grid_widget_1_to_n</a> : Version of the application that uses a grid widget to edit the Students->Courses relationship on the Student edit form. Still using a 1:n relationship.</li>
<li><a href="https://github.com/shannah/xftips-tutorial-easier-relationships/tree/grid_widget_n_to_m" target="_blank">grid_widget_n_to_m</a> : Changes the table structure and relationship to n:m; still using grid widget for editing the relationship.</li>
<li><a href="https://github.com/shannah/xftips-tutorial-easier-relationships/tree/checkbox_widget" target="_blank">checkbox_widget</a> : Same as the grid_widget_n_to_m branch except using a checkbox widget for adding/removing related records on the Student edit form.</li>
</ol>
<br />
<br /></div>
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com9tag:blogger.com,1999:blog-428033837951124899.post-87843559617609441172013-05-16T09:29:00.001-07:002013-05-16T09:36:29.243-07:00Using the "category" Directive to Place Actions in the Xataface UI<div>
<iframe allowfullscreen="" frameborder="0" height="360" src="http://www.youtube.com/embed/4_juhPkbRIc" width="480"></iframe>
</div>
Actions are a core concept of Xataface. They can be viewed from two perspectives:<br />
<br />
<ol>
<li>An action can be manifested as a button or link in the user interface. E.g. The "New Record" button is an action as it is rendered in the user interface.</li>
<li>An action can be thought of as an HTTP request handler. E.g. When the URL's query parameters include <i>-action=new</i>, the new record action is called upon to handle the request. This will render a new record form or process the input from a previously rendered new record form depending on the other parameters of the HTTP request.</li>
</ol>
<div>
In this tutorial, I want to focus on the first aspect: how and where actions are rendered in the Xataface UI.</div>
<div>
<br /></div>
<h3>
Creating an Action</h3>
<div>
To start out, we'll need to create a new action. </div>
<h4>
Steps:</h4>
<div>
<ol>
<li>Create a file named <i>actions.ini</i> inside the main directory of your application (if it doesn't already exist).</li>
<li>Add the following anywhere inside the <i>actions.ini</i> file:<br />
<script src="https://gist.github.com/anonymous/5580012.js"></script>
</li>
<li>Now try opening your application and notice the "Hello" button appear along with the other table actions. <table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjcqItohuz0kImqrmERE4gnzebkF1OBixKzCDzJBZJ6dr6KymWb62RfpRqlhyy2TS-1CTz-p5kBSRaZvbstT4z6VBQK9RUaM3a7YFTKTFLlsTjonJwUaxBjRGQnx5O82eYcu3aJFiCIqf7/s1600/Screen+Shot+2013-05-14+at+3.06.56+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjcqItohuz0kImqrmERE4gnzebkF1OBixKzCDzJBZJ6dr6KymWb62RfpRqlhyy2TS-1CTz-p5kBSRaZvbstT4z6VBQK9RUaM3a7YFTKTFLlsTjonJwUaxBjRGQnx5O82eYcu3aJFiCIqf7/s320/Screen+Shot+2013-05-14+at+3.06.56+PM.png" width="280" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">After adding the hello action to the table_actions category, it appears with other buttons in the table actions (upper left). This example shows the button using the g2 theme. It would appear differently in different themes.</td></tr>
</tbody></table>
</li>
</ol>
<div>
The placement of the action (in the upper left) was dictated by the category directive. I chose to place the action in the "table_actions" category. This category is intended to be used for actions that don't pertain to a specific record, but may be accessed at any time for a table. Some examples include:</div>
</div>
<div>
<ul>
<li>The new record action</li>
<li>The Import action</li>
</ul>
<div>
Currently clicking on the "Hello" button doesn't actually do anything because we haven't assigned it a URL (the url property). At this point I just want to demonstrate how to show, hide, and locate the action buttons. Future articles may discuss other aspects of actions like how to specify the URL that they point to.</div>
</div>
<div>
<br /></div>
<h3>
Alternative Action Placement</h3>
<div>
You may be wondering what other possible positions are there for an action. We've already seen the "table_actions" category, but there are many others. In fact you can even create your own categories and embed actions inside the Xataface UI using your own custom templates.</div>
<div>
Some other built-in categories include:</div>
<div>
<ul>
<li><b>result_list_actions</b><br />This category is intended for actions that operate on records in the <i>list</i> view. They are rendered at the top and bottom of the list view.<br /><br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1HisB9-lyRKMMZ-T4IjBi1ekhN8Ui9cIAULKDcuteTA6KB7QlkupnV9JSpgEBYLxirdep6mPJ8HbIBCVZ0KqmCRo0gJvAhrUzkzFjiNnjVJbxvMuf6dcBYj31yuS_OedSzl1gNWtMbFg1/s1600/Screen+Shot+2013-05-14+at+3.16.25+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="87" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1HisB9-lyRKMMZ-T4IjBi1ekhN8Ui9cIAULKDcuteTA6KB7QlkupnV9JSpgEBYLxirdep6mPJ8HbIBCVZ0KqmCRo0gJvAhrUzkzFjiNnjVJbxvMuf6dcBYj31yuS_OedSzl1gNWtMbFg1/s400/Screen+Shot+2013-05-14+at+3.16.25+PM.png" width="400" /></a></div>
</li>
<li><b>record_actions</b><br />This category is intended for actions that operate on the current record shown in details view. They are rendered along the top bar of the record details view (which is used for rendering the output of most actions that pertain to a single record).<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8O6zjXsfVU-bKmm7KatJdf-6UYI8FJ5niE4LovNVFoUMXeCSphp3ywhB4PBsG9S5foHceRq9mIpce4YGqOn7ZNH1UAtEjOrUuIYbRP2yvQjpib-k_fthvomXGcFoE9TmvjFLtkO0ZmXvP/s1600/Screen+Shot+2013-05-14+at+3.18.28+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="302" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8O6zjXsfVU-bKmm7KatJdf-6UYI8FJ5niE4LovNVFoUMXeCSphp3ywhB4PBsG9S5foHceRq9mIpce4YGqOn7ZNH1UAtEjOrUuIYbRP2yvQjpib-k_fthvomXGcFoE9TmvjFLtkO0ZmXvP/s400/Screen+Shot+2013-05-14+at+3.18.28+PM.png" width="400" /></a></div>
</li>
<li><b>related_list_actions</b><br />This category is intended for actions that operate on a list of related records. They are rendered at the top and bottom of a related list. This is very similar to the <i>result_list_actions</i> category, except that it is used for related lists.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrTqYzp0bhkZqfCaE7tj-l2rDYe_0A-jEjj-WjfvldgOSXO_UbZm0bXBRRyByDlbatkNJNa8jfWzeRzWWyXhVH92aj3KpRJgyQhsr7xiMkn_D10TxGGZLVP67mPbPMg3UH7pZK9AgrrEOh/s1600/Screen+Shot+2013-05-14+at+3.21.14+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgrTqYzp0bhkZqfCaE7tj-l2rDYe_0A-jEjj-WjfvldgOSXO_UbZm0bXBRRyByDlbatkNJNa8jfWzeRzWWyXhVH92aj3KpRJgyQhsr7xiMkn_D10TxGGZLVP67mPbPMg3UH7pZK9AgrrEOh/s400/Screen+Shot+2013-05-14+at+3.21.14+PM.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Notice that the "hello" action is rendered inside the "More" menu. This drop-down is automatically created by Xataface to house excess actions in the case that there are too many actions in the category to fit the space nicely.</td></tr>
</tbody></table>
</li>
<li><b>find_actions</b><br />This category is intended for alternative "find" actions. When a user enters a search in the top search field, they will be sent to the list view by default with a filter on the results. You can provide optional responses to the search if you like, but creating your own custom search actions.<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2YE11yj6WgBUJm5BhNAqdm127J09_3MX8A4cDEyqfXrYHWtWp-tMao0GJqT456o1717kexF9DNA6PBnkcewPZ3O7T0lsLUQm9w4EE4t0qZEiLZz3Mua7NYa9D8EpsLm_WSFecVew77M4z/s1600/Screen+Shot+2013-05-14+at+3.27.53+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="86" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2YE11yj6WgBUJm5BhNAqdm127J09_3MX8A4cDEyqfXrYHWtWp-tMao0GJqT456o1717kexF9DNA6PBnkcewPZ3O7T0lsLUQm9w4EE4t0qZEiLZz3Mua7NYa9D8EpsLm_WSFecVew77M4z/s400/Screen+Shot+2013-05-14+at+3.27.53+PM.png" width="400" /></a></div>
</li>
<li><b>history_record_actions</b><br />These are actions that are intended to operate on history snapshots of a record. This is only relevant if your application has history enabled. It will appear next to the record version in the history tab for the record.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO0-UB60vOVfA0t15V3PN3X01sMWG_M08lBkH9LcycqUV4blMS2DSvdFSRnewWVws5jwt1bVrMvcgz2sW3ae0YYs3Dqf87A_ygHHOnK2uMotls17BRJJS5lRwtqFbrNzCajaGJ1wuoXNgU/s1600/Screen+Shot+2013-05-14+at+3.42.14+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="162" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjO0-UB60vOVfA0t15V3PN3X01sMWG_M08lBkH9LcycqUV4blMS2DSvdFSRnewWVws5jwt1bVrMvcgz2sW3ae0YYs3Dqf87A_ygHHOnK2uMotls17BRJJS5lRwtqFbrNzCajaGJ1wuoXNgU/s400/Screen+Shot+2013-05-14+at+3.42.14+PM.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Notice the "hello" label appears next to the history snapshot ID 3.</td></tr>
</tbody></table>
</li>
<li><b>personal_tools</b><br />This category is intended for actions that pertain to a user's personal preferences or account. These will be rendered in the user's <i>personal</i> menu in the upper right along with actions like "Edit Profile", and "Log Out". <div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCaFvd77iJORIXmuoy2qa9QPo11dSLw0D5G2X90AfIeo1bUUignj7xf1N3HHBgsCe5J6wRf6JdWT2xH3T9gxk0XlJYcBUW90CQ0Q7tQvAC-ARQKXvwSilPPOmpv_zHH5eoRQMJCXKhmuWO/s1600/Screen+Shot+2013-05-14+at+3.44.44+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjCaFvd77iJORIXmuoy2qa9QPo11dSLw0D5G2X90AfIeo1bUUignj7xf1N3HHBgsCe5J6wRf6JdWT2xH3T9gxk0XlJYcBUW90CQ0Q7tQvAC-ARQKXvwSilPPOmpv_zHH5eoRQMJCXKhmuWO/s1600/Screen+Shot+2013-05-14+at+3.44.44+PM.png" /></a></div>
</li>
<li><b>management_actions</b><br />This category is intended for actions that help administrators manage system settings. They are rendered in the upper right in the "Control Panel" menu.<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTIMKNqdsPJTfueUv8iKPDt5ayttN8BlrQY3tYr744_L1EeGpDqIEvfFV8j2fF-UE141ifZsW5CaNwC0c0gM82PZ5CkRzpVBQLNe_pFiMEb3ggMs5au8du-Mi02z6kGuuyyDauKRv5IpSW/s1600/Screen+Shot+2013-05-14+at+3.47.39+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTIMKNqdsPJTfueUv8iKPDt5ayttN8BlrQY3tYr744_L1EeGpDqIEvfFV8j2fF-UE141ifZsW5CaNwC0c0gM82PZ5CkRzpVBQLNe_pFiMEb3ggMs5au8du-Mi02z6kGuuyyDauKRv5IpSW/s1600/Screen+Shot+2013-05-14+at+3.47.39+PM.png" /></a></div>
</li>
<li><b>top_right_menu_bar</b><br />This category is intended for actions that have global significance. They are rendered in the top right on every page. The only actions that are rendered here by default are the personal tools menu and the control panel menu. (<a href="http://xataface-tips.blogspot.ca/2013/05/updating-xataface-applications-to-use.html">g2 theme only</a>)<br /><br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy_xAozR8z7F4VXUaFx20qMvFfz4F6kFS_Howct6mDnGVji_6a4vrjRQNkboL3ziS0NSg97hOCpcBJxvHHIV-dtM8qgJXW1o5XX4CDQZgBUpra57WXmD3_3xZolRtL0PV6lvw-TXB1ADk6/s1600/Screen+Shot+2013-05-14+at+3.49.44+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="115" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy_xAozR8z7F4VXUaFx20qMvFfz4F6kFS_Howct6mDnGVji_6a4vrjRQNkboL3ziS0NSg97hOCpcBJxvHHIV-dtM8qgJXW1o5XX4CDQZgBUpra57WXmD3_3xZolRtL0PV6lvw-TXB1ADk6/s400/Screen+Shot+2013-05-14+at+3.49.44+PM.png" width="400" /></a></div>
</li>
<li><b>top_left_menu_bar</b><br />This category renders the action in the top left menu bar of the application, along with the list of tables in the app. It is a convenient way of adding additional options to the tables menu.<br />(<a href="http://xataface-tips.blogspot.ca/2013/05/updating-xataface-applications-to-use.html">g2 theme only</a>).<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-AfqYssDdVzX-_hbZ2QGgz1Lzm9LiRjdHpzHiqRHVKkZJE38pLZ1IrTq3_O58hzU5tuFR5OEzOD5BjEoeVbpmsdo5lXrYop5h9Po9oB6FoqwoD-Rftx4UDhKyQjnQkltd5dDpY8Ok5H-2/s1600/Screen+Shot+2013-05-14+at+3.52.27+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="175" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-AfqYssDdVzX-_hbZ2QGgz1Lzm9LiRjdHpzHiqRHVKkZJE38pLZ1IrTq3_O58hzU5tuFR5OEzOD5BjEoeVbpmsdo5lXrYop5h9Po9oB6FoqwoD-Rftx4UDhKyQjnQkltd5dDpY8Ok5H-2/s400/Screen+Shot+2013-05-14+at+3.52.27+PM.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Action added to the top_left_menu_bar category. Because there are already a lot of actions in this category, it has been added to the "More" drop-down menu. Had there been fewer existing actions, this would have been rendered in the top menu bar directly.</td></tr>
</tbody></table>
</li>
<li><b>list_export_actions</b><br />This category is intended for actions that export the current result list in a different format. It will be rendered inside the "Export" drop-down menu above and below the list view, along with actions like "Export CSV" and "Export XML".<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitmL1RKBbw80TqkeLCUJJGhM1t4vLa-kJqCFJ1RmhE992IiVyZf7-6nYfPYKinizoQEGGg2OGUmrtH3IyfmeXlRmemyknXPhHx5IZ4TNKISth-ucXIjp9mNw2wM1rB35sAPA6tp_zk3fJ4/s1600/Screen+Shot+2013-05-14+at+3.55.06+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="125" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitmL1RKBbw80TqkeLCUJJGhM1t4vLa-kJqCFJ1RmhE992IiVyZf7-6nYfPYKinizoQEGGg2OGUmrtH3IyfmeXlRmemyknXPhHx5IZ4TNKISth-ucXIjp9mNw2wM1rB35sAPA6tp_zk3fJ4/s320/Screen+Shot+2013-05-14+at+3.55.06+PM.png" width="320" /></a></div>
</li>
<li><b>record_export_actions</b><br />This category is intended for actions that export the current record (shown in details view) in a different format. It will be rendered in the "Export" drop-down menu of the record details page, along with actions like "Export CSV" and "Export XML". <div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOo1mbhQicphWZoG9R7a5xTQaLIsiM0Kx4UlNhQWdW28NdlLWpVVo7W7RK4KDgvj4M8fWrX9KO4SOr4wrhr0tAfHkUoKpZ8ydrySPVx2Lc4AJxE9tWb2o5BiebgZlx7v0s1w8wraI5WlOP/s1600/Screen+Shot+2013-05-14+at+3.56.35+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOo1mbhQicphWZoG9R7a5xTQaLIsiM0Kx4UlNhQWdW28NdlLWpVVo7W7RK4KDgvj4M8fWrX9KO4SOr4wrhr0tAfHkUoKpZ8ydrySPVx2Lc4AJxE9tWb2o5BiebgZlx7v0s1w8wraI5WlOP/s400/Screen+Shot+2013-05-14+at+3.56.35+PM.png" width="400" /></a></div>
</li>
<li><b>related_export_actions</b><br />This category is just like the <i>list_export_actions</i> category except it is for actions that export the current related record list in a different format. It is rendered in the "Export" menu of the related records view.<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Oqx4bfBqlT7ST-kb97m-IzfEpoNbmGCSmW36vjKYg1GavwX-WyAHCiWJWOkPk8mJJijpj_Z7SBP2f6851p8H8qeDLEQPbkofxnJaXo3wBmB5Y_jiEqbLQrWbCBTvSoILHQ-MctjUrxfp/s1600/Screen+Shot+2013-05-14+at+3.59.23+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Oqx4bfBqlT7ST-kb97m-IzfEpoNbmGCSmW36vjKYg1GavwX-WyAHCiWJWOkPk8mJJijpj_Z7SBP2f6851p8H8qeDLEQPbkofxnJaXo3wBmB5Y_jiEqbLQrWbCBTvSoILHQ-MctjUrxfp/s400/Screen+Shot+2013-05-14+at+3.59.23+PM.png" width="400" /></a></div>
</li>
</ul>
<div>
There are more actions than just these. A good way to discover what action categories are available to you is to open the Xataface <a href="http://weblite.ca/svn/dataface/core/trunk/actions.ini">actions.ini </a>file and the <a href="http://weblite.ca/svn/dataface/modules/g2/trunk/actions.ini">g2 module actions.ini file</a> and see what categories are used in the existing actions.</div>
</div>
<div>
<br /></div>
<h3>
More information about actions</h3>
<div>
This is just the tip of the iceberg for Xataface actions. This post has likely created as many questions as it has answered. There is more information about actions in the <a href="http://xataface.com/wiki">Xataface wiki</a> and in the <a href="http://xataface.com/documentation/tutorial/getting_started">Getting Started guide</a>. E.g.</div>
<div>
<ul>
<li><a href="http://xataface.com/wiki/actions.ini_file">actions.ini file directives</a> (reference)</li>
<li><a href="http://xataface.com/documentation/tutorial/getting_started/dataface_actions">Getting Started With Actions</a></li>
</ul>
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com114tag:blogger.com,1999:blog-428033837951124899.post-82031581421790006372013-05-13T09:24:00.001-07:002013-05-13T09:27:06.861-07:00Best Practice for Xataface Permissions : NO ACCESS Unless ....<a href="http://xataface.com/">Xataface</a> has a rich <a href="http://xataface.com/documentation/tutorial/getting_started/permissions">permissions system</a> that offers quite a bit of flexibility in how you implement permissions. A typical pattern for implementing permissions in Xataface is to add a "role" field to the users table to specify the user's role, and use this inside your getPermissions() methods to help determine what permissions should be granted to the user.<br />
<br />
Many developers will take this pattern and then implement a getPermissions() method in their application delegate class similar to the following:<br />
<br />
<script src="https://gist.github.com/anonymous/5569452.js"></script>
To summarize this strategy, it entails:
<br />
<br />
<ol>
<li>Returning the permissions assigned to the current user's role (which would be defined in the permissions.ini file).</li>
<li>Returning no permissions if either the user isn't logged in or they don't have value in their 'role' field.</li>
</ol>
<div>
While this approach may work, it could open up some security holes if you're not <i>very</i> careful. Since you have defined the permissions in the application delegate class, these will be used as the default permissions for <i>every</i> table in the database. If you have some tables that include private information (which will likely be the majority of your tables), you would need to explicitly provide more limited permissions on each of those tables individually by implementing getPermissions() methods in their respective delegate classes.</div>
<div>
<br /></div>
<h3>
A Better Approach : Default NO ACCESS; Grant More As Needed</h3>
<div>
A better approach would be to grant NO ACCESS to all users, except for administrative users, at the application level. Then you can grant additional permissions as needed at the table level. E.g.</div>
<div>
<script src="https://gist.github.com/anonymous/5569564.js"></script>
</div>
<div>
This grants users with a role of 'ADMIN', all permissions in the system, and no access to anyone else.
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com37tag:blogger.com,1999:blog-428033837951124899.post-40304465943784918222013-05-11T13:27:00.000-07:002013-05-11T13:27:01.693-07:00How to use Chrome and CSS to Customize the Xataface Look & Feel<iframe allowfullscreen="" frameborder="0" height="320" src="http://www.youtube.com/embed/mKqmhdpQSCE" width="480"></iframe>
<div>
<br /></div>
<a href="http://xataface.com/">Xataface</a> provides you (the developer) with many tools to customize the user interface. Some options include:<br />
<br />
<ul>
<li>Implementing Blocks & Slots in delegate classes.</li>
<li>Overriding templates.</li>
<li>Configuration directives in the various INI files.</li>
<li>Preferences.</li>
<li>Permissions & Authentication.</li>
<li>CSS</li>
<li>Javascript</li>
</ul>
<div>
All of these strategies have a time and place. Before rolling up your sleeves and delving into templates, blocks, slots, preferences, and the like, I recommend seeing how far you can get with just pure CSS by creating your own custom stylesheet.</div>
<div>
<br /></div>
<div>
Using CSS, you can change the logo, hide components on the page, change color schemes, and even modify the layout to a certain extent. Using a modern web browser like Chrome that includes some developer tools, it is fairly easy to peruse the DOM and come up with the appropriate CSS to modify it to your liking.</div>
<div>
<br /></div>
<div>
In this post I will show you how to change the logo and hide unnecessary sections of the UI by:</div>
<div>
<ol>
<li>Adding a custom CSS stylesheet to your Xataface application.</li>
<li>Using Chrome's developer tools to identify the parts of the page that you want to modify or hide.</li>
<li>Modifying or hiding portions of the UI that we identified in the previous step by adding CSS rules to our CSS file.</li>
</ol>
<h4>
Including a Custom Stylesheet</h4>
</div>
<div>
We start by creating a blank CSS file in our application's directory. We'll call it "style.css". Then we need to tell Xataface to include this file in the <head> of all pages that are rendered in the app. We do this inside the beforeHandleRequest() method of the Application delegate class (since this is run before every request is handled so we can ensure that the code will be executed:<br />
<script src="https://gist.github.com/shannah/5560187.js"></script>
We make use of the Dataface_Application class's addHeadContent() method that allows you to embed arbitrary content in the <head> section. We use it here to add a <link> tag which includes our stylesheet.</div>
<div>
<br />
<h4>
Changing the Logo</h4>
</div>
<div>
In this example, I'm using the <a href="http://weblite.ca/svn/dataface/modules/g2/trunk">G2 theme</a>. Instructions for other themes will be slightly different because the logo is displayed in different positions using different methods. </div>
<div>
<br /></div>
<div>
Steps:</div>
<div>
<ol>
<li>Open your Xataface application in Chrome.</li>
<li>Right click on the Xataface logo, and select "Inspect Element".<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAoREcXb8po7i4613y_bJToAclKp1X4JzWQjpkA0LMKdohtcsyuO5g8tsENduzQVwasxxWUtZAV6_3Ybdf3PPqYD9GD61KcFZTplx7GNQXcbu_0GcRiEuic80WxMKmnpa3eYg40GXjCxb-/s1600/Screen+Shot+2013-05-11+at+8.57.42+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAoREcXb8po7i4613y_bJToAclKp1X4JzWQjpkA0LMKdohtcsyuO5g8tsENduzQVwasxxWUtZAV6_3Ybdf3PPqYD9GD61KcFZTplx7GNQXcbu_0GcRiEuic80WxMKmnpa3eYg40GXjCxb-/s1600/Screen+Shot+2013-05-11+at+8.57.42+AM.png" /></a></div>
</li>
<li>This will open the document inspector so that you can actually see the HTML document tree. If you got here by right-clicking on the Xataface logo, then it should show up with the xataface logo div tag selected.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkrVhRzuJLoAKrjNFCg3gMztvV_cXfiLu3dSNvEG4kv0ms8vksxWBXM3N2f4lF7_QoRxjtKj_juY9hyphenhyphenba4BFAEyGz006Y0oqMD0a1Rn12SDwPwHoDQhlawn7IYwRlWzT7a4qdOmdJwzfHx/s1600/Screen+Shot+2013-05-11+at+8.56.09+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="374" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkrVhRzuJLoAKrjNFCg3gMztvV_cXfiLu3dSNvEG4kv0ms8vksxWBXM3N2f4lF7_QoRxjtKj_juY9hyphenhyphenba4BFAEyGz006Y0oqMD0a1Rn12SDwPwHoDQhlawn7IYwRlWzT7a4qdOmdJwzfHx/s640/Screen+Shot+2013-05-11+at+8.56.09+AM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The Chrome element inspector allows you to view the DOM tree. Here you see the xf-logo div seelcted. On the right you can see some of the CSS rules that apply to this element, including the background-image directive which specifies the image file to use for the logo.</td></tr>
</tbody></table>
</li>
<li>With the <span style="font-family: Courier New, Courier, monospace;">xf-logo</span> div selected you should be able to review the CSS rules that apply to that tag. The first matched rule (seen in the lower right of the above screenshot) specifies that the background-image is at <i>images/small-xataface-logo.png</i>. This gives us a clue as to how to craft our own CSS rule to override this background-image. Let's add a CSS rule in our <i>style.css</i> file to specify a different image file.<br /><br /><span style="font-family: Courier New, Courier, monospace;">#xf-logo {<br /> background-image: url(images/custom-logo.png);<br />}</span><br /><br />Note that URLs in a CSS file are relative to the location of the stylesheet. Therefore, we need to place our <i>custom-logo.png</i> file inside the "images" directory which should be at the same level as the <i>style.css</i> file (i.e. in the main site directory).</li>
<li>Reload our page in the browser and see if the change worked:<br /><br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji7KlFWBm61ZHrAgXszNhKDMMGEw8i7gL1Pa1ze79G0VROXkj_sOiArez9u6aIRIFmyk70vCvaa_V-Zfsjt7n7pFikE8_eqDuVQuAJgiKS364CJAdmeOYT8RpKaxWVAg0N54e_gQ3hiC0D/s1600/Screen+Shot+2013-05-11+at+9.13.08+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji7KlFWBm61ZHrAgXszNhKDMMGEw8i7gL1Pa1ze79G0VROXkj_sOiArez9u6aIRIFmyk70vCvaa_V-Zfsjt7n7pFikE8_eqDuVQuAJgiKS364CJAdmeOYT8RpKaxWVAg0N54e_gQ3hiC0D/s1600/Screen+Shot+2013-05-11+at+9.13.08+AM.png" /></a></div>
</li>
<li>It looks like it worked, but since our new logo is a different size than the old one, we need to change the dimensions of the <i>xf-logo</i> div tag, so let's make the following change in the style.css file:<br />
<script src="https://gist.github.com/shannah/5560436.js"></script>
</li>
<li>And now refresh the page to see the change:<br /><br /><div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi55pq8Q07FYKTyf1TxAHIG176_aU2MxfymhR5Sn3xVzoPyZDFqQPdiMSz7wA9BgBWedT9fOOBs8OiC0DBrBoN415I9GHf6_QsPM_yjQBJlyZBpZjyKvy_Oa_mtftEzYIoLw3I0HxQx_ufX/s1600/Screen+Shot+2013-05-11+at+9.19.35+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi55pq8Q07FYKTyf1TxAHIG176_aU2MxfymhR5Sn3xVzoPyZDFqQPdiMSz7wA9BgBWedT9fOOBs8OiC0DBrBoN415I9GHf6_QsPM_yjQBJlyZBpZjyKvy_Oa_mtftEzYIoLw3I0HxQx_ufX/s1600/Screen+Shot+2013-05-11+at+9.19.35+AM.png" /></a></div>
</li>
<li>You may want to tinker a little more with the CSS to adjust the position of the logo, but we're pretty much there.</li>
</ol>
<h3>
Hiding Sections of the Page</h3>
</div>
<div>
Another common request is to hide sections of the page that your users don't need to access. Keep in mind that there is a difference between your users not <i>needing</i> to access a feature and not being <i>permitted</i> to access a feature is very important.</div>
<div>
<b>Never rely on CSS to hide features that the user shouldn't have access to. Use permissions for this. Only use CSS to hide sections that aren't needed.</b></div>
<div>
<b><br /></b></div>
<div>
That said, let's take a look at the "View" page for a record, and make some decisions about which parts of the page are necessary, and which parts we want to keep. </div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGDkSUzFYfOaoGOSouE-oxJ58i2cyQFl6tUz2Y0nJkjRAijLCNQxXmV20qLHTmBkmkVA8ie95vlNV489NFt1Wd-sPHIMkrq3vsAiBnfBxHiCb1jXf7CrsAXQPjIAfGb9vSEZXil1JeKV8O/s1600/Screen+Shot+2013-05-11+at+9.28.59+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGDkSUzFYfOaoGOSouE-oxJ58i2cyQFl6tUz2Y0nJkjRAijLCNQxXmV20qLHTmBkmkVA8ie95vlNV489NFt1Wd-sPHIMkrq3vsAiBnfBxHiCb1jXf7CrsAXQPjIAfGb9vSEZXil1JeKV8O/s640/Screen+Shot+2013-05-11+at+9.28.59+AM.png" width="640" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
As an example, let's take an extreme approach and hide everything except what appears inside the "Details" portlet. No menus. No logos. No search box, etc...</div>
<div>
<h4>
Removing the Left Column</h4>
<div>
<ol>
<li>Let's use Chrome again to inspect the document so we can figure our how to target the left column. We just need to right click somewhere in the left column and select "Inspect Element" option to open the element inspector. We can then hover over the various tags and see some feedback in the browser panel as the representation of tags on which you hover become selected.<br /><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD6spkrAQNMDPYfI4vURnhyphenhyphen1fiaKsQfjXzrSjp9q_23KMjutjrKY3YEQ2m6lHbfGLUbaI_YShGYuwa8P53lwT-KaqYLdIX5yMf0zirlCXB7eLpDe3GTUs0sCgwHo2qMKqPPbcLx-LF7udM/s1600/Screen+Shot+2013-05-11+at+9.32.37+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="386" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD6spkrAQNMDPYfI4vURnhyphenhyphen1fiaKsQfjXzrSjp9q_23KMjutjrKY3YEQ2m6lHbfGLUbaI_YShGYuwa8P53lwT-KaqYLdIX5yMf0zirlCXB7eLpDe3GTUs0sCgwHo2qMKqPPbcLx-LF7udM/s400/Screen+Shot+2013-05-11+at+9.32.37+AM.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Hovering over the left_column div tag in the element inspector causes the left column to appear selected in the browser panel. This allows you to hover around and identify the tags that you want to hide or modify.</td></tr>
</tbody></table>
</li>
<li>We see that the id of the left column <i><td></i> tag is "left_column", so we can hide this in our style.css file by adding:<br />
<script src="https://gist.github.com/shannah/5560592.js"></script>
<br />
Now, reloading the page we see that the left column is now gone. <div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8M9twDo1UpVtUH3uG2yTqzTLyTQRtNLtHTRRpqx8dwi9gBJeZ3IENxMAqTQkgp_v2hHVjHwqq6Nh2ocLWwk5n3ZSfkyeTT-rUtP5lAC9bbj0i-CZiVMHVXeyDMFsnZbKbZ1YuY5KUMZvy/s1600/Screen+Shot+2013-05-11+at+10.02.40+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="425" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8M9twDo1UpVtUH3uG2yTqzTLyTQRtNLtHTRRpqx8dwi9gBJeZ3IENxMAqTQkgp_v2hHVjHwqq6Nh2ocLWwk5n3ZSfkyeTT-rUtP5lAC9bbj0i-CZiVMHVXeyDMFsnZbKbZ1YuY5KUMZvy/s640/Screen+Shot+2013-05-11+at+10.02.40+AM.png" width="640" /></a></div>
</li>
<li>Now, employing similar investigation techniques, we remove the top menu, the search box, the internal left column (i.e. with the tree menus and the record search), by adding the following to our CSS file:
<script src="https://gist.github.com/shannah/5560664.js"></script>
<br />
And the result is:
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg923Fwn_9vf73KOORnCi_yXPv5NwgEdrb3m8_k0akBrnVjtTFl6FYt8-Q9W4S-e9xAOXQfzHLkM6OZeYhYME3NlCdm7AjI7rHWr-ejJU3q6vLyomHJUsJb3bizcozmL6rNisMyIWoHAVIi/s1600/Screen+Shot+2013-05-11+at+10.16.16+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="419" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg923Fwn_9vf73KOORnCi_yXPv5NwgEdrb3m8_k0akBrnVjtTFl6FYt8-Q9W4S-e9xAOXQfzHLkM6OZeYhYME3NlCdm7AjI7rHWr-ejJU3q6vLyomHJUsJb3bizcozmL6rNisMyIWoHAVIi/s640/Screen+Shot+2013-05-11+at+10.16.16+AM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">With all of the sections removed, we are left with just the portions of the page that we want to display.</td></tr>
</tbody></table>
</li>
</ol>
<div>
There is much more that can be done with CSS to customize the look and feel of a <a href="http://xataface.com/">Xataface</a> application. We have only scratched the surface here. I'll let you run with it from here to see what your creativity spawns.</div>
</div>
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com33tag:blogger.com,1999:blog-428033837951124899.post-41297765568001872652013-05-11T07:08:00.002-07:002013-05-11T07:31:58.089-07:00Create Your Own Maillist Manager using the Xataface Email Module<div>
<iframe allowfullscreen="" frameborder="0" height="380" src="http://www.youtube.com/embed/apMpoMIr6gw" width="550"></iframe>
</div>
<div>
<br /></div>
<div>
Suppose you have a "contacts" table in your database that contains contact information of all of your customers or users. It is probable that you intend to contact these by email - e.g. for a mailing list. The <a href="http://xataface.com/dox/modules/Email/latest/">Xataface Email module</a> makes it a simple matter to use this table as a mailing list.</div>
<div>
<br /></div>
<h4>
What it Does</h4>
<div>
The<a href="http://xataface.com/dox/modules/Email/latest/"> Email module</a> adds a "Send Email" action to all designated tables that allows you to send email to all records in the current found set.</div>
<div>
<br /></div>
<h4>
Features</h4>
<div>
<ul>
<li>HTML Email</li>
<li>Attachments</li>
<li>Email templates</li>
<li>Compatible with all existing found sets. (E.g. you can perform a search for any subset of rows, and the module will send to the found set).</li>
</ul>
<h4>
Prerequisites</h4>
</div>
<div>
<ul>
<li><a href="http://xataface.com/">Xataface 2.0 or higher</a></li>
<li>The <a href="http://xataface.com/dox/modules/ckeditor/latest/">ckeditor Module</a> needs to be installed (for HTML email)</li>
</ul>
<h4>
Installation</h4>
</div>
<div>
<ol>
<li>Create a modules directory inside your application's directory, if it doesn't exist already:<br /><br /><span style="font-family: Courier New, Courier, monospace;">$ cd /path/to/my/app<br />$ mkdir modules</span></li>
<li>Check out the latest <a href="http://weblite.ca/svn/dataface/modules/ckeditor/trunk">SVN trunk</a> of the <a href="http://xataface.com/dox/modules/ckeditor/latest/">ckeditor module</a> into the modules directory:<br /><br /><span style="font-family: Courier New, Courier, monospace;">$ cd modules<br />$ svn co http://weblite.ca/svn/dataface/modules/ckeditor/trunk ckeditor</span></li>
<li>Check out the latest <a href="http://weblite.ca/svn/dataface/modules/Email/trunk">SVN trunk</a> of the <a href="http://xataface.com/dox/modules/Email/latest/">Email module</a> into the modules directory<br /><br /><span style="font-family: Courier New, Courier, monospace;">$ svn co http://weblite.ca/svn/dataface/modules/Email/trunk Email</span></li>
<li>Activate both the <a href="http://xataface.com/dox/modules/ckeditor/latest/">ckeditor</a> and <a href="http://xataface.com/dox/modules/Email/latest/">Email </a>modules in the conf.ini file by adding the following to the [_modules] section:<br /><br /><span style="font-family: Courier New, Courier, monospace;">modules_ckeditor=modules/ckeditor/ckeditor.php<br />modules_Email=modules/Email/Email.php</span></li>
<li>Designate the tables on which you want the "Send Email" action to be available by marking them as implementing the Person ontology. You do this by adding the following to the beginning of the fields.ini file of each table that should be designated:<br /><br /><span style="font-family: Courier New, Courier, monospace;">[__implements__]<br /> Person=1</span></li>
<li>In addition, you may need to specify which field of the table contains the email address. You do this by adding the <span style="font-family: Courier New, Courier, monospace;">email=1</span> directive to the field's definition in the fields.ini file. E.g:<br /><br /><span style="font-family: Courier New, Courier, monospace;">[my_email_field]<br /> email=1</span><br /><br />If you don't do this, Xataface will have to try and guess which field to use - and it may be wrong!</li>
</ol>
<h4>
Enabling Attachments</h4>
</div>
<div>
If you want to be able to send attachments with your email messages, you will need to follow these additional installation steps:</div>
<div>
<ol>
<li>Install the <a href="http://xataface.com/dox/modules/ajax_upload/latest/">ajax_upload module</a>. You can do this by navigating to the modules directory, and checking out the<a href="http://weblite.ca/svn/dataface/modules/ajax_upload/trunk"> latest from SVN</a>:<br /><br /><span style="font-family: Courier New, Courier, monospace;">$ cd modules<br />$ svn co http://weblite.ca/svn/dataface/modules/ajax_upload/trunk ajax_upload</span></li>
<li>Enable the <a href="http://xataface.com/dox/modules/ajax_upload/latest/">ajax_upload module</a> in your conf.ini file by adding the following to the [_modules] section:<br /><br /><span style="font-family: Courier New, Courier, monospace;">modules_ajax_upload=modules/ajax_upload/ajax_upload.php</span></li>
<li>Specify the location of the directory to store the uploaded files, by adding the following to your conf.ini file:<br /><br /><span style="font-family: Courier New, Courier, monospace;">[modules_Email]<br /> attachments=path/to/attachments<br /> attachments_url=url/to/attachments</span></li>
</ol>
<h3>
Sending Email</h3>
</div>
<div>
Now that we have the module installed, let's try to send some email. In our example application, I have a "contacts" table with all of my contacts.<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggEWmRV5j-Flykgk8V6TnA35ir5V3dgZojsBbvsHKDeSZognx2YiSqT_vF8ePz_3CbXg-rRPnscrgKeteUjyxidrrJ18Q6IF54dr3XRt_YwSiuId50pIQuwtjyH5-N36NtXvzUQ_bf11Qj/s1600/Screen+Shot+2013-05-09+at+9.54.04+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggEWmRV5j-Flykgk8V6TnA35ir5V3dgZojsBbvsHKDeSZognx2YiSqT_vF8ePz_3CbXg-rRPnscrgKeteUjyxidrrJ18Q6IF54dr3XRt_YwSiuId50pIQuwtjyH5-N36NtXvzUQ_bf11Qj/s640/Screen+Shot+2013-05-09+at+9.54.04+AM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The "List" view for the contacts table. Notice the "Send Email" button, which is present because the Email module is installed and the contacts table is designated as a "Person" entity.</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
</div>
<div>
You can see from the result descriptor in the upper right that the current found set has 1155 records. If you want to send an email to all 1155 contacts, just click the "Send Email" button at the top. This will open the email form:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2qlYywIX_LRnapCXM75Jgg29qeYEcz47qN7HxKcjImkhD76O-Eu6wa3aY6F8oPizA_29-793toK607SRiKMzbGMHKl9jCtMpLj8CGJjNHdrmGbOiaYrhKN9TDY8O-dFIAovnOADZ_OTq9/s1600/Screen+Shot+2013-05-10+at+7.12.54+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2qlYywIX_LRnapCXM75Jgg29qeYEcz47qN7HxKcjImkhD76O-Eu6wa3aY6F8oPizA_29-793toK607SRiKMzbGMHKl9jCtMpLj8CGJjNHdrmGbOiaYrhKN9TDY8O-dFIAovnOADZ_OTq9/s1600/Screen+Shot+2013-05-10+at+7.12.54+AM.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The email form. The Message body uses CKeditor to allow you to craft an HTML email in WYSIWYG fashion.</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Before proceeding with sending the email, you should double check that you are sending to the found set that you intend. Look at the description at the top of the form that says "Please complete the form below to send email to the 1155 email addresses...", and ensure that the number looks right. You should also click the "Show Email Addresses" link to reveal a list of the email addresses that will be mailed. The worst thing you can do is accidentally send an email to thousands of people.</div>
<div class="separator" style="clear: both; text-align: left;">
If you accidentally click "Send", you can still cancel or pause the email queue at any time - so there are some fail safes in place.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
So let's send a simple email to all of our found set:</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiGLZD69tNwZCMGoTUgOALPdKhtHdbHeaIa2nSL1dbSA2UbNg7YPpo6Zyd9POrC5Okzwyl9dX0pExAdQCA9jb4arpLCFg2AmMpPWnAspJvg3E7u2e5rmr1ZXSuTAD7Q3og8TZpsFLhqZgX/s1600/Screen+Shot+2013-05-10+at+7.22.10+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiGLZD69tNwZCMGoTUgOALPdKhtHdbHeaIa2nSL1dbSA2UbNg7YPpo6Zyd9POrC5Okzwyl9dX0pExAdQCA9jb4arpLCFg2AmMpPWnAspJvg3E7u2e5rmr1ZXSuTAD7Q3og8TZpsFLhqZgX/s640/Screen+Shot+2013-05-10+at+7.22.10+AM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A simple email by filling in the subject, from, and message body fields. Notice that the "From" field should include the email address in the standard email notation: Name <address>.</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: left;">
Now that we're ready, click the "Send Now" button:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-nTSb7ALJXSc476BzJVjY7yjsxgmMZ88z1L7ck2c6LnWrE8mU6yEp-ypQYSYhwnXFrpWeuwyrxMCQ9X1XhCLt7ltnCjGx3L-bpKBnnH4sn0kbbEXfEZhMhZ16vHneRBJKRiF5DkN2m67e/s1600/Screen+Shot+2013-05-10+at+7.23.55+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-nTSb7ALJXSc476BzJVjY7yjsxgmMZ88z1L7ck2c6LnWrE8mU6yEp-ypQYSYhwnXFrpWeuwyrxMCQ9X1XhCLt7ltnCjGx3L-bpKBnnH4sn0kbbEXfEZhMhZ16vHneRBJKRiF5DkN2m67e/s1600/Screen+Shot+2013-05-10+at+7.23.55+AM.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
This will show an email progress form to allow you to track the progress of the mailout:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0IFtRbG4rdfzKV8P7EyCiCuLlf7rfwHAXYTO7Y57Hg_XryyfgKdQpLmaF_zy20uwqxi3Mm5OCOp9xnRvNBQ_NEZp2ZDUPjaqBnrBWi-jqTwtWr2lpspoWzKYgvnk0I8H2zF7-dp8WRoXs/s1600/Screen+Shot+2013-05-10+at+7.29.50+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0IFtRbG4rdfzKV8P7EyCiCuLlf7rfwHAXYTO7Y57Hg_XryyfgKdQpLmaF_zy20uwqxi3Mm5OCOp9xnRvNBQ_NEZp2ZDUPjaqBnrBWi-jqTwtWr2lpspoWzKYgvnk0I8H2zF7-dp8WRoXs/s640/Screen+Shot+2013-05-10+at+7.29.50+AM.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
You can cancel/pause the mailout at any time by clicking the "Cancel" button. Clicking this button will take you to a "Job Paused" screen:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggJDSkYl8h7HV_wnACoVPblLXfR7HHAlnSmNF7EQuKZPqBJbk2TGrRF-4SUs_3kg5QA4LMQ2j-sHummit-AJnnfXB4r3jLeqVa3-MqeKZ34Ccm7eVJx5NNGDST1B5VhRlP6lOXKm2RvLoI/s1600/Screen+Shot+2013-05-10+at+7.30.15+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggJDSkYl8h7HV_wnACoVPblLXfR7HHAlnSmNF7EQuKZPqBJbk2TGrRF-4SUs_3kg5QA4LMQ2j-sHummit-AJnnfXB4r3jLeqVa3-MqeKZ34Ccm7eVJx5NNGDST1B5VhRlP6lOXKm2RvLoI/s640/Screen+Shot+2013-05-10+at+7.30.15+AM.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
You can resume the job by clicking "Resume Job", and you can also view the mail log to see a detailed log of all of the email that has already been sent (by clicking "View Mail Log"):</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAX7h2-I2tm7P7ChgVoFt8KMVBPrqusihitLe1JzbrwX0Sa0n-UUE6VHKmmkg9TBBMydKj2Thyphenhyphen4P0XUtY_NWHeZWgKapnv_FWhaAHj94tzclvv_4TVdbJoA-dBuSLAgy7UhxsdmskAiiHP/s1600/Screen+Shot+2013-05-10+at+7.30.27+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="440" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAX7h2-I2tm7P7ChgVoFt8KMVBPrqusihitLe1JzbrwX0Sa0n-UUE6VHKmmkg9TBBMydKj2Thyphenhyphen4P0XUtY_NWHeZWgKapnv_FWhaAHj94tzclvv_4TVdbJoA-dBuSLAgy7UhxsdmskAiiHP/s640/Screen+Shot+2013-05-10+at+7.30.27+AM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The email history log shows every email that has been sent, along with status information about the Email.</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
In my example email here, it sent out 96 emails successfully before I paused it. Logging into my email program, I can view the email:</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWU_2KgW-JSkQLa2UL_F4bWYqLxSYeHQSW0WQLK6R_lUszdmNidDJ4qPLLG7ITjMy4JNU2w8AWuP9U4TAt_tpDE7XwOefJma3IZzl6uagv_bGdfBLdJuDJX6Qs5QiG-e5hrfMcXdLMePUa/s1600/Screen+Shot+2013-05-10+at+7.31.06+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="412" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWU_2KgW-JSkQLa2UL_F4bWYqLxSYeHQSW0WQLK6R_lUszdmNidDJ4qPLLG7ITjMy4JNU2w8AWuP9U4TAt_tpDE7XwOefJma3IZzl6uagv_bGdfBLdJuDJX6Qs5QiG-e5hrfMcXdLMePUa/s640/Screen+Shot+2013-05-10+at+7.31.06+AM.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The resulting email as opened in my Email reader</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h4>
Opting Out of the Mail List</h4>
<div class="separator" style="clear: both; text-align: left;">
Notice that the mailer automatically added a line at the bottom of the email to give the recipient the option to "Opt out" of the mailing list. This "opt-out" feature is built into the Email module and is handled automatically for you. The module keeps a "black list" of email addresses that should not receive email. When a batch email is created, it automatically skips any recipient addresses on this black list (unless you explicitly tell the Email module to ignore the black list on the email form).</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Just to see what happens, I'm going to click on the link to opt out:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN4ZysVRM4kjFqPU-XfY_jfUgqMDzB4DbRpoV0JQhz1w0q1wwwlkrmYx39I9gA0R_yaYwsUWfJpMMBRQ9QSy1LFSZabNX2yrNN_FY5K-o3ht9V53kABf5-BcdHFncJZlS08QZ8edpx-U3g/s1600/Screen+Shot+2013-05-10+at+1.21.40+PM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN4ZysVRM4kjFqPU-XfY_jfUgqMDzB4DbRpoV0JQhz1w0q1wwwlkrmYx39I9gA0R_yaYwsUWfJpMMBRQ9QSy1LFSZabNX2yrNN_FY5K-o3ht9V53kABf5-BcdHFncJZlS08QZ8edpx-U3g/s1600/Screen+Shot+2013-05-10+at+1.21.40+PM.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Form to opt out of receiving email notifications</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
This form gives the user an opportunity to reconsider opting out. If they are sure that they don't want to receive email from this list, they can click the button on this form. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
They can opt back in at any time by clicking the same link that is in the email.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h4>
Customizing the Opt-Out Instructions</h4>
</div>
<div>
In some cases you might not want the user to see this opt-out message, or you may want to customize it to fit your purposes. This can be done using either a conf.ini file directive or a delegate class method. For this example, let's just use the conf.ini file directives. You can simply add the following section, and sub-directives to your conf.ini file:</div>
<div>
<br /></div>
<div>
<div class="line" style="-webkit-transition: background-color 0.5s, box-shadow; font-family: monospace, fixed; font-size: 13px; line-height: 1; margin: 0px; min-height: 13px; padding-bottom: 0px; padding-left: 53px; text-indent: -53px; transition: background-color 0.5s, box-shadow; white-space: pre-wrap; word-wrap: break-word;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">[<a class="code" href="http://xataface.com/dox/modules/Email/latest/classmodules___email.html" style="color: #4665a2; text-decoration: none;">modules_Email</a>]</span></div>
<div class="line" style="-webkit-transition: background-color 0.5s, box-shadow; font-family: monospace, fixed; font-size: 13px; line-height: 1; margin: 0px; min-height: 13px; padding-bottom: 0px; padding-left: 53px; text-indent: -53px; transition: background-color 0.5s, box-shadow; white-space: pre-wrap; word-wrap: break-word;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">opt_out_html = <span class="stringliteral" style="color: #002080;">"<hr/><p>Click <a href='$url'>here</a> to opt out of our list.</p>"</span></span></div>
<div class="line" style="-webkit-transition: background-color 0.5s, box-shadow; font-family: monospace, fixed; font-size: 13px; line-height: 1; margin: 0px; min-height: 13px; padding-bottom: 0px; padding-left: 53px; text-indent: -53px; transition: background-color 0.5s, box-shadow; white-space: pre-wrap; word-wrap: break-word;">
<span style="font-family: Courier New, Courier, monospace; font-size: xx-small;">opt_out_text = <span class="stringliteral" style="color: #002080;">"\r\n\r\n----------------------\r\n To opt out, go to $url \r\n"</span></span></div>
</div>
<div class="line" style="-webkit-transition: background-color 0.5s, box-shadow; font-family: monospace, fixed; font-size: 13px; line-height: 1; margin: 0px; min-height: 13px; padding-bottom: 0px; padding-left: 53px; text-indent: -53px; transition: background-color 0.5s, box-shadow; white-space: pre-wrap; word-wrap: break-word;">
<span class="stringliteral" style="background-color: white; color: #002080;"><br /></span></div>
<span style="background-color: white;">You can specify any text you want here. Just embed the $url variable in the content where you want the link to the opt-out page to appear.</span>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com44tag:blogger.com,1999:blog-428033837951124899.post-52579825907376021682013-05-08T08:57:00.001-07:002014-06-25T15:06:07.560-07:00Updating Xataface Applications to Use the G2 Theme<span style="background-color: #ffe599; font-size: large;">NOTE: As of version 2.1, the g2 theme is installed by default so the steps described in this article are no longer necessary.</span><br />
<br />
The default <a href="http://xataface.com/">Xataface</a> theme was built on the old Plone 2.0 stylesheet from 2005. Safe to say, it looks its age. Clearly, Xataface needs a face lift. The only question was how to introduce the modifications. There were two primary options to choose from:<br />
<br />
<ol>
<li>Modify the core Xataface templates and stylesheets.</li>
<li>Create an optional module that overrides the default templates and stylesheets.</li>
</ol>
<div>
In order to cause minimal disruption to existing Xataface applications, I chose the latter, and the solution is embodied in the g2 module - which includes new Javascripts, Stylesheets, and templates that look a little more modern.</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjopzR4u1tmdJafQJAi4ibELNt8O2t69wUGUgimJsRIZ99dN8U2vxgLLpBODKZJ1YBhjDT1ExreKYIZYQi3Fi_tsUlL9wmuRkTtaCzHBHQdz76QpwaO8hTdSRckVXISIY-KnxXfix6Lv-pH/s1600/Screen+Shot+2013-05-08+at+8.34.53+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjopzR4u1tmdJafQJAi4ibELNt8O2t69wUGUgimJsRIZ99dN8U2vxgLLpBODKZJ1YBhjDT1ExreKYIZYQi3Fi_tsUlL9wmuRkTtaCzHBHQdz76QpwaO8hTdSRckVXISIY-KnxXfix6Lv-pH/s640/Screen+Shot+2013-05-08+at+8.34.53+AM.png" height="425" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The default Xataface theme. Notice the tables list on the left and vertical because there are more than 10 tables included in the tables menu.</td></tr>
</tbody></table>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHni9KE_EYFmEpLy4XDJn7_a9T1WsaZcNwN1tgAG5_QWJ_ed-nkaCf3DUdOAD7Nn7SOHS34NBqhtwXYzvIluv_k3EKgl1KsibwpDeTIeM6MxCnlbvVtM-PWrthiSTFNqOf4NgFjqj0FEq8/s1600/Screen+Shot+2013-05-08+at+8.36.59+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHni9KE_EYFmEpLy4XDJn7_a9T1WsaZcNwN1tgAG5_QWJ_ed-nkaCf3DUdOAD7Nn7SOHS34NBqhtwXYzvIluv_k3EKgl1KsibwpDeTIeM6MxCnlbvVtM-PWrthiSTFNqOf4NgFjqj0FEq8/s640/Screen+Shot+2013-05-08+at+8.36.59+AM.png" height="434" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Same page using the G2 theme. Notice the tables listed horizontally along the top. The "More" drop-down menu lists the remaining tables.</td></tr>
</tbody></table>
<div>
<br /></div>
<div>
<br /></div>
<div>
Some features of the g2 module include:</div>
<div>
<ul>
<li><b>jQuery</b>. jQuery is now included by default - so it is available to all scripts and Javascript modules.</li>
<li><b>Advanced Find form</b>. The advanced find form is now accessible using a dynamic Javascript pull-down effect so that users don't have to navigate away from the existing list or details page in order to perform a find.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGA2yllZ-EC9BhTtpBU7jKucub7v-sdFYNtUemecYbOrwPrp8oJ82TxD4eLfBaAaYt1IjLS_rzsX1ImhmfVix0XQxEY5Wr6muyrSpfxKc9tZ7PmeKqSrj3ltkt8qqxcSL6aDIbN7jjWW9O/s1600/Screen+Shot+2013-05-08+at+8.41.16+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGA2yllZ-EC9BhTtpBU7jKucub7v-sdFYNtUemecYbOrwPrp8oJ82TxD4eLfBaAaYt1IjLS_rzsX1ImhmfVix0XQxEY5Wr6muyrSpfxKc9tZ7PmeKqSrj3ltkt8qqxcSL6aDIbN7jjWW9O/s640/Screen+Shot+2013-05-08+at+8.41.16+AM.png" height="492" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The Advanced find form appears via pull-down.</td></tr>
</tbody></table>
</li>
<li><b>Selected Record Actions</b>. Result list actions are now all dialed in to operate on the records that are "checked" in list view.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpzKAycjb8CAk11MhhEmp7un0Rfepo4YMfQThNNLtd7SR9xmj0Q16bJyIQE1L5DBrGhe8_KucDoAnSsmI77xXzBQMRefhoMyI_9bEVTqHuDyI38E7ssExaSDKIOby9EJzbxIk2uZsbFu31/s1600/Screen+Shot+2013-05-08+at+8.44.20+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpzKAycjb8CAk11MhhEmp7un0Rfepo4YMfQThNNLtd7SR9xmj0Q16bJyIQE1L5DBrGhe8_KucDoAnSsmI77xXzBQMRefhoMyI_9bEVTqHuDyI38E7ssExaSDKIOby9EJzbxIk2uZsbFu31/s640/Screen+Shot+2013-05-08+at+8.44.20+AM.png" height="292" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The list view now shows result list actions across the top and bottom of the list. These all operate on the selected rows (i.e. the rows whose checkboxes are checked).</td></tr>
</tbody></table>
</li>
<li><b>Result controller</b>. The result controller is now less cluttered, and only gives users option to change the "limit" and "start" when they click on the result details.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNeyhafPkw3NVAL-wCPs5nSULqOQii9x5zZNcSXho-4n5eyFedHVAld-WiCPIJkheJcMY9cdb0F6Cj1BUvfcEgEHqo8UzVb8G1yLen49EFELxQqcuWRzwi5F0Y3X1S7Sv9upwXyW7pU4My/s1600/Screen+Shot+2013-05-08+at+8.44.40+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNeyhafPkw3NVAL-wCPs5nSULqOQii9x5zZNcSXho-4n5eyFedHVAld-WiCPIJkheJcMY9cdb0F6Cj1BUvfcEgEHqo8UzVb8G1yLen49EFELxQqcuWRzwi5F0Y3X1S7Sv9upwXyW7pU4My/s320/Screen+Shot+2013-05-08+at+8.44.40+AM.png" height="123" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The result list controller is more streamlined. The "1-11 of 11" label is all that appears. Clicking on that displays the pop-up dialog to modify the "skip" and "limit" settings.</td></tr>
</tbody></table>
</li>
<li><b>More actions...</b> Action lists are now limited to 7 actions in a particular category. Additional actions, are then included in an 8th "More" drop-down menu. This makes the interface far more extensible when there are large numbers of tables.<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWeC85dG_GGm-ESN8b1gEeix9JdQ4X1G6xaqssJKqx-ds-xxvGhnPPf9ZkHbreG5ExN26hB0EgOYHx4K0yu5AM3F6fSOsNlf6BEqVbNLWp4bjofMXiRobXRqMydNadoBSbbLiQT3NvxCht/s1600/Screen+Shot+2013-05-08+at+8.47.53+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWeC85dG_GGm-ESN8b1gEeix9JdQ4X1G6xaqssJKqx-ds-xxvGhnPPf9ZkHbreG5ExN26hB0EgOYHx4K0yu5AM3F6fSOsNlf6BEqVbNLWp4bjofMXiRobXRqMydNadoBSbbLiQT3NvxCht/s320/Screen+Shot+2013-05-08+at+8.47.53+AM.png" height="236" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Now excess tables are listed in the "More..." menu automatically. This is the case with all action groups.</td></tr>
</tbody></table>
</li>
</ul>
<div>
<br /></div>
<h3>
How To Install</h3>
</div>
<div>
<ol>
<li>Create a "modules" directory in your application if one doesn't already exist.<br /><span style="font-family: Courier New, Courier, monospace;">$ cd /path/to/myapp<br />$ mkdir modules</span></li>
<li>Check out the latest g2 module from the subversion repository, into the "modules" directory.<br /><span style="font-family: Courier New, Courier, monospace;">$ svn co http://weblite.ca/svn/dataface/modules/g2/trunk g2</span></li>
<li>Add the following to the [_modules] section of your application's conf.ini file.:<br /><span style="font-family: Courier New, Courier, monospace;">modules_g2=modules/g2/g2.php</span></li>
<li><span style="font-family: inherit;">Clean out the templates_c directory to remove old cached templates.</span><br /><span style="font-family: Courier New, Courier, monospace;">$ rm -rf templates_c/*</span></li>
</ol>
<h3>
<span style="font-family: inherit;">Video Tutorial</span></h3>
</div>
<div>
<span style="font-family: inherit;"><br /></span>
<iframe allowfullscreen="" frameborder="0" height="480" src="//www.youtube.com/embed/ehXIeIEoaeA" width="640"></iframe>
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com39tag:blogger.com,1999:blog-428033837951124899.post-63191998687002736722013-05-07T10:20:00.001-07:002013-05-10T15:34:47.741-07:00Using the Xataface Tagger Widget for n:m relationshipsThe <a href="http://xataface.com/dox/modules/tagger/latest/">Xataface tagger widget</a> allows you to add and remove records from a many-to-many relationship with a simple "Tag" interface. This widget is meant to be used with a transient field (similar to the grid widget) so that users can manipulate the records in the relationship directly on the edit/new record form.<br />
<br />
The user is able to type into a text field free form, and is assisted by auto-complete suggestions. If they select one of the auto-complete suggestions, then they will be adding a link to an existing record. If they don't choose one of the suggestions, they will be creating a new record in the related table and adding it to the relationship.<br />
<br />
Since a single string isn't sufficient (in most cases) to create a new record (e.g. which field should the string be saved in?), you may need to implement that <span style="font-family: Courier New, Courier, monospace;">fieldname__addTag()</span> method in the table delegate class to help guide how a tag should be added.<br />
<br />
After the tag has been created, the user can double-click the tag to open the record's edit form and make further modifications.<br />
<br />
<h3>
Prerequisites</h3>
<div>
<ul>
<li>You'll need to be running <a href="http://xataface.com/">Xataface 2.0</a> or higher.</li>
</ul>
</div>
<h3>
Installation</h3>
<div>
<ol>
<li>Create a directory named "modules" inside your application's folder, if it doesn't already exist. (e.g. /path/to/my/app/modules).<br /><code>$ cd /path/to/my/app<br />$ mkdir modules</code></li>
<li>Change directories to the modules directory that you just created:<br /><code>$ cd modules</code></li>
<li>Check out the latest source of the tagger module from the SVN trunk:<br /><code>$ svn co http://weblite.ca/svn/dataface/modules/tagger/trunk tagger</code><br />When you are done you should have the tagger source in the directory modules/tagger. Ensure that you have modules/tagger/tagger.php, etc.... If not, you need to move it or rename it so that the path is correct.</li>
<li>Add a <code>[_modules]</code> section to your application's conf.ini file, if it doesn't already exist.</li>
<li>Add a line in the <code>[_modules]</code> section of your conf.ini file as follows:<br /><code>modules_tagger=modules/tagger/tagger.php</code> </li>
</ol>
</div>
<h3>
Configuring a Relationship to use the Tagger Widget</h3>
<div>
Now that the tagger module is installed, we can set up our n:m relationships to be editable via a tagger widget on the edit form of the parent record.</div>
<div>
For example, suppose we have a database with a "cities" table and a "articles" table, where cities can be associated with multiple articles and vice-versa. I.e. There is an n:m relationship between cities and articles.</div>
<div>
The cities relationship is defined in the articles table relationships.ini file as follows:</div>
<div>
<code></code><br />
<pre><code>[cities]
cities.id = articlecity.city_id
articlecity.article_id = "$id"
</code></pre>
<br />
You can see that this is a n:m relationship (among other things) because it makes use of a join table named articlecity to join them together.<br />
<br />
<h4>
Steps to Set up Tagger Widget on Cities relationship:</h4>
<div>
<ol>
<li>Add a transient field to the articles table's fields.ini file as follows:<br />
<code><pre>[cities]
widget:type=tagger
relationship=cities
transient=1
tagger_label=name
</pre>
</code>
<br />
This says that we are creating a transient field (i.e. a field that doesn't really exist in the database, but should appear on the edit and new record forms) named "cities" using the "tagger" widget. The relationship directive sets the widget to work on the "cities" relationship. The tagger_label directive specifies which field from the "cities" table should be used as the text that is displayed in each tag.</li>
<li>Load the "New Article" form in your browser to make sure that the tagger field shows up. If you receive errors, you should check your PHP error log to find out what the error is. If it worked it will look almost like a normal text field:<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMlfH_3V5CpaH2d_kHJQaNnYNzbRCKksb8mx7oImUguzM55rF17PYM0YbkpdzyAUuUTQCDa12MRnMm3B2eeD_5yeRTysz7kIclWSkxNeK4jnnj3bv_IEZ6hhxEuh-2w6f9DFjpL9is63fi/s1600/Screen+Shot+2013-05-07+at+7.46.25+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMlfH_3V5CpaH2d_kHJQaNnYNzbRCKksb8mx7oImUguzM55rF17PYM0YbkpdzyAUuUTQCDa12MRnMm3B2eeD_5yeRTysz7kIclWSkxNeK4jnnj3bv_IEZ6hhxEuh-2w6f9DFjpL9is63fi/s1600/Screen+Shot+2013-05-07+at+7.46.25+AM.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The "Cities" field rendered with the tagger widget.</td></tr>
</tbody></table>
When you start typing in the field, it will give you auto-complete suggestions as follows:<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUHXx-GfSW4WmicwVONcOsvawSCweJkUwKFowjnWh1UB0fMfps85jX1Dg-7n8Zs1i2UiJvOc01wSlkCufa72p7nR56EkPoykc-cPZ1V_wUxxk0D1HTkVZLwoQGDfLdjfOZC22nYj1jfeQF/s1600/Screen+Shot+2013-05-07+at+7.48.40+AM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUHXx-GfSW4WmicwVONcOsvawSCweJkUwKFowjnWh1UB0fMfps85jX1Dg-7n8Zs1i2UiJvOc01wSlkCufa72p7nR56EkPoykc-cPZ1V_wUxxk0D1HTkVZLwoQGDfLdjfOZC22nYj1jfeQF/s1600/Screen+Shot+2013-05-07+at+7.48.40+AM.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Auto-complete in tagger widget. If it finds any matching records in the related table, it will show them here.<br />
<br /></td></tr>
</tbody></table>
</li>
<li>Implement the <span style="font-family: Courier New, Courier, monospace;">cities__addTag()</span> method in the articles delegate class. At this point (before implementing <code>cities__addTag()</code>), you should be able to add existing cities as tags without error. But if you try to enter a new city, you'll receive a little error indicator because it wasn't able to add the city to the cities table. This is because the cities table has other <code>NOT NULL</code> fields that need to be filled in, other than the city name. E.g. it needs a province or state.<br />The <span style="font-family: Courier New, Courier, monospace;">cities__addTag()</span> method. Our method looks like:<br /><br /><code><pre>function cities__addTag(Dataface_Record $record, $value){
$city = new Dataface_Record('cities', array());
$city->setValues(array(
'prov_state_id' => DEFAULT_PROV_ID,
'name' => $value,
'url' => strtolower($value)
));
return $city;
}
</pre>
</code><br />
This method takes the "article" record as the first parameter, and the string value of the tag to add as the 2nd parameter. In this case, we are creating a new Dataface_Record for the cities table with the name specified by $value, and a default province ID.</li>
<li>Test out adding a new tag. You can do this by clicking on "New Article", then start to type in the "Cities" field.<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfi3vWhyRfa4Szf6C0n4ynP_G1xNS5xdk7Q50EEO44eaQK7PbUubzxs5pQ-CnL8_W9yng67GxtuWiuTkBVuC9cj9p1PqV6EA9dhaGIEN34koVQAc2fc32I6PBFvN3tz9aS-6w0UAyHOQFu/s1600/Screen+Shot+2013-05-07+at+9.03.56+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="62" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfi3vWhyRfa4Szf6C0n4ynP_G1xNS5xdk7Q50EEO44eaQK7PbUubzxs5pQ-CnL8_W9yng67GxtuWiuTkBVuC9cj9p1PqV6EA9dhaGIEN34koVQAc2fc32I6PBFvN3tz9aS-6w0UAyHOQFu/s320/Screen+Shot+2013-05-07+at+9.03.56+AM.png" width="320" /></a></div>
</li>
<li>You should also be able to double-click the tag to reveal the edit form for the city:<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVL9DgxNtLFga4MjMUXzvobHk8Yx_6JkF88I6pNWPo0MuFYVtJbRMk-HHoSw7Vo1SVz4w0TAh0HRVTQuQc5OZVDKjN3JckrxPdqJmsyZKK9sswUAFDS2al_FPhaEkkBCBJ5K7n6tKPRTSx/s1600/Screen+Shot+2013-05-07+at+9.06.56+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="218" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVL9DgxNtLFga4MjMUXzvobHk8Yx_6JkF88I6pNWPo0MuFYVtJbRMk-HHoSw7Vo1SVz4w0TAh0HRVTQuQc5OZVDKjN3JckrxPdqJmsyZKK9sswUAFDS2al_FPhaEkkBCBJ5K7n6tKPRTSx/s400/Screen+Shot+2013-05-07+at+9.06.56+AM.png" width="400" /></a></div>
</li>
</ol>
</div>
<div>
<h3>
Video Tutorial</h3>
</div>
</div>
<div>
<iframe allowfullscreen="" frameborder="0" height="480" src="http://www.youtube.com/embed/L04j3u_UZyc" width="640"></iframe>
</div>
Anonymoushttp://www.blogger.com/profile/03137243274155905995noreply@blogger.com258