## FAQ: Fields/columns/attributes can be added in the constructor, in `meta()`, and in `postMeta()`. When should each be used? ### Constructor Discouraged (??? why?) ### `meta()` As much as possible should be defined in `meta()` so that the results can be cached. (Presumably this enables nodes to be created faster.) this leads to faster node If you can defined Should be declared "static" so that it can be cached. (The system appears to use Things that don't work: * Constants, including flags. ### - discouraged (??? why?) * `meta()` - should be defined "static" so can be cached * `postMeta()` - if the fields added depend on the data itself In an atkMetaNode, what is the difference between `meta` and `postMeta`? `meta` is cached (if declared static, which it should be), `postMeta` can do more stuff. ## FAQ: Why don't flag constants work in `meta()`? Unlike `postMeta`, which loads the ## FAQ: When should fields be added in `postMeta`, and when in the constructor? (Adding a field in `postMeta` that has already been added "reconfigures" the existing field, but doesn't changes its order.) ## FAQ: How can I control the order in which attributes (fields/columns) appear an edit form? You pretty much have to set it explicitly, either via `atkMetaPolicy::setNodeOrder()`, `atkMetaPolicy::setIncludes()` or the `$order` arguments to `atkMetaNode::add()` or `atkMetaPolicy::add()`. Both `atkMetaNode::add()` and `atkMetaPolicy::add()` appear to preserve the order attributes are added, but in reality the attributes have been added before you get a chance to call `add`, and your call merely reconfigures an existing attribute. You **can** change the order by calling `atkMetaNode::add()` from your constructor, but adding fields in the constructor is discouraged for some reason. ## FAQ: How can I find the id an attribute will get when rendered as HTML? (Useful for writing JavaScript code that displays or hides a attributes as values change.) ## FAQ: How to translate to/from the `meta()`/`postMeta()` ways of adding attributes? public function postMeta() { $this->add(new atkMyClass($column_name, $arg1, $arg2, $arg3)); } corresponds to public static function meta($policy) { $policy->add($column_name, "atkMyClass", array($arg1, $arg2, $arg3)); } Note: Whatever arguments need to be provided to the `atkMyClass` constructor **must** also be provided in the array that is the third argument to `atkMetaPolicy::add()`. The documentation for `atkMetaPolicy::add()` lists `$flags` as an optional fourth argument, and `$order` as an optional sixth argument, suggesting that these are somehow automatically passed to the `atkMyClass` constructor when the time comes to instantiate the object. However, this is not the case. (I don't know what happens if e.g. `$flags` is specified in both places. Presumably one overwrites the other?) Note: In `meta()`, you may be required to need to manually load flag constants. For example, if you want to use the flag `AF_BOOL_INLINE_LABEL` you'll need to do this: useattrib('atkboolattribute'); class MyClass { // ... } ## FAQ: How to create a "dummy" attribute? For example, to create an "attribute" whose value is derived from other values. See [atkDummyAttribute](http://www.atk-framework.com/wiki/AtkDummyAttribute). You can also transform a conventional attribute into one that never touches the database by setting the following flags: $attr->setLoadType(NOLOAD) $attr>setStorageType(NOSTORE) ## FAQ: How do you determine which attributes/columns appear in "list" view? To add attributes from a OneToOne or ManyToOne relation, use `addListColumns`: // in postMeta() $this->getAttribute('artist')->addListColumn('name'); (There doesn't seem to be an analogous way to remove columns.) ## FAQ: How do you determine which attributes/columns appear in "detail" view? ## TIP: How to change or translate ATK-generated (template) text (Including labels.) Some text is defined explicly in code, whilst other bits come automagically from language files. (Initial first capital letter conversion.) * attributename * link_attributename_add * link_attributename_edit * link_select_attributename * tab_attributename * nodename_attributename_tooltip * `adminHeader()` * `adminFooter()` * * ## FAQ: How do I "fake" an attribute? There are a few ways of doing this. The simplest way, which works if you want (or need) complete control over the HTML output of the label and value, is to use an `atkDummyAttribute`: // in meta() $policy->add("myattribute", "atkDummyAttribute", array("my value", AF_DUMMY_SHOW_LABEL)); (The value of the label is defined in the language file.) You need to supply the AF_DUMMY_SHOW_LABEL flag because by default, atkDummyAttributes don't display a label. Note that $policy->addFlag('requested_article', AF_DUMMY_SHOW_LABEL); **won't** work, because `AF_DUMMY_SHOW_LABEL` isn't a regular flag; it's a message to the constructor to either add the `AF_BLANKLABEL` flag (or not). If the value is dynamic (e.g. derived from another field), then you can add an appropriate `*_edit()` method: public function myattribute_edit() { return "some text"; } If you want ATK to generate some of the HTML for you (for example, if your "fake" value is a date, and you want ATK's date picker to be displayed), then you need to create the appropriate attribute and configure it to never touch the database: // in postMeta() $att = $this->add(new atkAttribute("myattribute")); $att->setLoadType(NOLOAD); $att->setStorageType(NOSTORE); In this case you can't use `myattribute_edit()` to generate the value, because `myattribute_edit()` is expected to return HTML. Instead, you need to modify `preAddToEditArray()`: public function preAddToEditArray(&$record) { $record["date"] = array ( 'day' => '11', 'month' => '07', 'year' => '2008', 'hours' => '12', 'minutes' => '11', 'seconds' => '00' ); } (Better to use `initial_values()` for this?) Although if appropriate to the situation, it's safer to use an built-in parser: public function preAddToEditArray(&$record) { $att = $this->getAttribute("date"); $record["date"] = $att->db2value(array_merge($record, array("date" => "2008-07-11 12:11:00"))); } Other types of "fake" attributes: * `atkParserAttribute` - ??? ## FAQ: How can I write an (raw) array to the database? You can commit a "raw" array to the database with `atkNode::addDb()`: $node = atkGetNode("users.customer"); $node->addDb(array( name => "Michael", email => "mjs@beebo.org" )); To update, ensure that the key `atkprimkey` is defined and call `updateDb` instead. You will probably need to explicitly tell the node which attribute has changed: public function postUpdate(&$record) { $record["status"] = 5; $this->getAttribute('status')->setForceUpdate(true); $this->updateDb($record); // ... } To select: $node = atkGetNode("users.customer"); $result = $node->selectDb(); ## FAQ: I'm trying to inherit from a node but it doesn't work as expected--what might be wrong? (i.e. my class fails the "empty subclass test".) Suppose you have: $node = newNode("foo.a"); ATK supports node inheritance whereby $node = newNode("bar.b"); will work exactly the same as before if class b extends a { } However, sometimes doesn't work. Here are a few pitfalls: 1. Some relations try to guess column names, which sometimes goes wrong when the class name changes. For example, if one of the relations is an `atkManyBoolRelation`, you may need to add a call to $bool->setLocalKey('a_id'); from the `postMeta()` of class `a` to ensure that this relation can be found from class `b`. (A similar problem may occur with `atkManyToManyRelation`.) The symptom of this problem a fatal error that's something like: Unknown column name (Unknown column 'project_supplier_selection.customer_project' in 'where clause'). You may get an error like this if you're trying to create a class, `customer_project` that is derived from `project`. (i.e. `class customer_project extends project { ... }`.) In this case, `project` has an attribute that's joined via a many-to-many relation, and the code is using the class name to guess the appropriate join column. (Note that the unknown column, `customer_project`, has the same name as the derived class.) In this case, the appropriate join column is `project_id`, and the solution to this problem is to make the join column name explicit: $this->setLocalKey('project_id'); 1. If a node name is not fully-qualified with the module name, then in some places ATK assumes that it is part the current node. e.g. if `a` defines a relation as follows: $policy->hasOne("quux"); then in `a` this will be interpreted as `foo.quux`, whilst in `b` it will be interpreted as `bar.quux`, which may not exist. (If this is the case, a symptom will be an editing screen with all the fields blank.) To fix this, fully qualify the module names: $policy->hasOne("foo.quux"); 1. If the base module has not been included, you'll get a "Class 'XXX' not found" error. To fix this, import the base class from `module_preload.inc`: atkimport('module.projects.project'); ## EXAMPLES: Using `hasOne()` The first argument to `hasOne()` is always a node name. If you have a table with the columns created_user_id updated_user_id both of which reference the `user` table, this can be modelled by either: $policy->add('created_user_id', 'atkManyToOneRelation', array('users.employee')); $policy->add('updated_user_id', 'atkManyToOneRelation', array('users.employee')); or $policy->hasOne('users.employee', array('source' => 'created_user_id')); $policy->hasOne('users.employee', array('source' => 'updated_user_id')); ## FAQ: What's the equivalent of `include` or `require` for modules? Use `atkimport` in the `module_preload.inc` file in the root of the module directory. e.g. to inherit from the node `projects.project` from the `customer_portal` module, create a `modules/customer_portal/module_preload.inc` file with the contents: ## FAQ: Raw SQL queries? $query = atkQuery::create(); $query->addTable('quote_tender_article', 'qta'); $query->addCondition('qta.quote_tender_id = ' . (int)$record['quote_tender_id']['id']); $query->addField('qta.requested_article_id'); $result = $query->executeSelect(); See . Important Types atkPage - atkNode::getPage() atkPage::register_script(); atkPage::register_loadscript(); /** * Get the page instance of the page on which the node can render output. * @return atkPage The page instance. */ function &getPage() { $page = &atkinstance("atk.ui.atkpage"); return $page; } /** * Get the ui instance for drawing and templating purposes. * * @return atkUi An atkUi instance for drawing and templating. */ function &getUi() { $ui = &atkinstance("atk.ui.atkui"); return $ui; } /** * Returns the lock mode. * * @return int lock mode (atkLock::EXCLUSIVE, atkLock::SHARED) */ public function getLockMode() { return $this->m_lockMode; } Flag subtleties: If constant undefined, flag doesn't get set. The role of magic. Flags used inconsistently--e.g. in a case where flag is checked in constructor only. ## HOWTO: Change text Use module-specific language file: $this->text('something'); Use global language file: atktext('something'); Use `setLabel` on an attribute: $attr->setLabel('something'); `setLabel` is the most specific, and will override other settings. This means that, for example, if a base class calls `setLabel` on an attribute in `postMeta()`, the only way to override it is to call `setLabel` in the derived class's `postMeta()`. ## FAQ: How to "manually" read/load/access a node/record? $node = newNode("moderation.deletereason"); $node->addFilter("moderation_delete_reason_id", 773); $record = $node->select(); // $record is an array ## FAQ: How to "manually" save a node/record? $node = newNode("moderation.deletereason"); $node->updateDb($record); // $record is an array atkGetDb()->commit(); // Transactions are not automatically committed! ## GLOSSARY * module * node * attribute ## FAQ: How can I construct a URL? There are several URL-building methods and functions: * `atkHref` (equivalent to `atkSessionManager::href`) * `dispatch_url` * `atkController::dispatchUrl` `atkHost` may also be useful if you need the host name.