121
irmtfan
Re: add some needed facilities for module developers in working with xoops database.
  • 2013/5/1 1:46

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Yes they are all needed.
we need many things.
Currently im stuck in another query because xoops is not capable to do it.
http://dev.mysql.com/doc/refman/4.1/en/insert.html
INSERT [LOW_PRIORITY DELAYED HIGH_PRIORITY] [IGNORE]
    [
INTOtbl_name [(col_name,...)]
    {
VALUES VALUE} ({expr | DEFAULT},...),(...),...
    [ 
ON DUPLICATE KEY UPDATE
      col_name
=expr
        
[, col_name=expr] ... ]

the ON DUPLICATE KEY UPDATE is very needed for a logger module. because i want to increment hits and i want to do it with efficiency.
Now we have to get all and when we find nothing then we insert otherwise increment.

So we need many facilities.

@redheadedrod :
Your work is really needed and i will lokking forward to be able to use it specially in xoops 2.6
try to implement any possible query.

@zyspec :
Yes i saw that request some months ago. please link this topic to that request too.

IMO there is a bug in core in class/database/database.php
function prefix($tablename '')
    {
        if (
$tablename != '') {
            return 
$this->prefix '_' $tablename;
        } else {
            return 
$this->prefix;
        }
    }
}


It will add the prefix more than one time.
to correct it i suggest this:
function prefix($tablename '')
    {
        if (
$tablename != '') {
    
// check if database prefix is not added yet and then add it!!!
    
if (strpos($tablename$this->prefix "_") !== 0) {
        
$tablename$this->prefix '_' $tablename;
    }

            return 
$tablename;
        } else {
            return 
$this->prefix;
        }
    }
}

Can we commit this to SVN?




122
irmtfan
add some needed facilities for module developers in working with xoops database.
  • 2013/4/30 11:31

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Currently i see many developers have to write many codes for some tasks like "show" "change" "drop" "add" in queries on "table", "table field" and "table index" in their modules. thru enhancing userlog module i decided to add these functions to XoopsObject class. so i want to share them with you and you save your precious time:
public function changeField($field null$structure null) {
    
$sql "ALTER TABLE {$this->table} CHANGE {$field} {$field} {$structure}";
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    return 
true;
}

public function 
showFields($field null) {
    
$sql "SHOW FIELDS FROM {$this->table}";
    if (isset(
$field)) {
        
$sql .= " LIKE '{$field}'";
    }
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    
$ret = array();
    while (
$myrow $this->db->fetchArray($result)) {
        
$ret[$myrow["Field"]] = $myrow;
    }
    return 
$ret;
}

public function 
addField($field null$structure null) {
    if (empty(
$field) || empty($structure)) {
        return 
false;
    }
    if (
$this->showFields($field)) {
        return 
false;
    } 
// field is exist
    
$sql "ALTER TABLE {$this->table} ADD {$field} {$structure}";
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    return 
true;
}

public function 
dropField($field null) {
    if (empty(
$field)) {
        return 
false;
    }
    if (!
$this->showFields($field)) {
        return 
false;
    } 
// field is not exist
    
$sql "ALTER TABLE {$this->table} DROP {$field}";
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    return 
true;
}

public function 
showIndex($index null) {
    
$sql "SHOW INDEX FROM {$this->table}";
    if (isset(
$index)) {
        
$sql .= " WHERE KEY_NAME = '{$index}'";
    }
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    
$ret = array();
    while (
$myrow $this->db->fetchArray($result)) {
        
$ret[] = $myrow;
    }
    return 
$ret;
}

public function 
addIndex($index null$fields = array()) {
    if (empty(
$index) || empty($fields)) {
        return 
false;
    }
    if (
$this->showIndex($index)) {
        return 
false;
    } 
// index is exist
    
$fields is_array($fields) ? implode(","$fields) : $fields;
    
$sql    "ALTER TABLE {$this->table} ADD INDEX {$index} ( {$fields} )";
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    return 
true;
}

public function 
dropIndex($index null) {
    if (empty(
$index)) {
        return 
false;
    }
    if (!
$this->showIndex($index)) {
        return 
false;
    } 
// index is not exist
    
$sql "ALTER TABLE {$this->table} DROP INDEX {$index}";
    if (!
$result $this->db->queryF($sql)) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    return 
true;
}

/**
 * Show if the object table or any other table is exist in database
 *
 * @access public
 * @param string $table or $db->prefix("{$table}") eg: $db->prefix("bb_forums") or "bb_forums" will return same result
 * @param bool   $found
 */
public function showTable($table null) {
    if (empty(
$table)) {
        
$table $this->table;
    } 
// the table for this object
    // check if database prefix is not added yet and then add it!!!
    
if (strpos($table$this->db->prefix "_") !== 0) {
        
$table $this->db->prefix("{$table}");
    }
    
$result $this->db->queryF("SHOW TABLES LIKE '{$table}'");
    
$found  $this->db->getRowsNum($result);
    return empty(
$found) ? false true;
}

/**
 * Rename an old table to the current object table in database
 *
 * @access public
 * @param string $oldTable or $db->prefix("{$oldTable}") eg: $db->prefix("bb_forums") or "bb_forums" will return same result
 * @param        bool
 */
public function renameTable($oldTable) {
    if (
$this->showTable() || !$this->showTable($oldTable)) {
        return 
false;
    } 
// table is current || oldTable is not exist
    // check if database prefix is not added yet and then add it!!!
    
if (strpos($oldTable$this->db->prefix "_") !== 0) {
        
$oldTable $this->db->prefix("{$oldTable}");
    }
    if (!
$result $this->db->queryF("ALTER TABLE {$oldTable} RENAME {$this->table}")) {
        
xoops_error($this->db->error() . '<br />' $sql);
        return 
false;
    }
    return 
true;
}
currently you can use them by copying them to your ModuleObject class which extend the XoopsObject so the usage is like this:
$MYMODULEHandler =& xoops_getmodulehandler('handler_name''MODULE_NAME');

$MYMODULEHandler->changeField("MY_FIELD""TEXT NOT null");

if(
$MYMODULEHandler->showTable()) print_r($MYMODULEHandler->table "is exist");

if(
$MYMODULEHandler->renameTable("MY_OLD_TABLE")) print_r(" upgrade to the new version correctly done")
but we can add them to XoopsObject or maybe to XoopsDatabaseFactory



123
irmtfan
Re: Upgrading issue from v2.5.5 to v2.5.6
  • 2013/4/30 2:26

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


I believe you have some issues with your include/license.php file. (it is damaged)

your simple solution is overwrite this file with the original from full package.



124
irmtfan
Re: XOOPS 2.5.6 Final Release Issues
  • 2013/4/30 2:23

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


good
IMO it is better to do these for an upgrade package:
- remove install, mainfile.php, include/license.php (removing license is important)
- remove robots.txt, xoops.css and favicon.ico (It will help webmasters to not loose customization)
- remove suico and zetagenesis from themes folder (just default theme is enough)
- remove cache and templates_c (no need for them)
- add upgrade folder


for my personal usage i will create another upgrade and i remove these too:
- images and uploads folder because i have customization there.

@chefry:

those red lines are not important because they just want to warn you about those folders permissions. while they are above 755 it is ok for the system.
but it is strange. usually you should see green lines when chmod is the same. I know in some servers users cannot change the chmod of a folder at 777 maybe your issue is this.




125
irmtfan
Re: ability to select group during register & changing group during editing profile
  • 2013/4/29 10:05

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


huum you are right. it is not possible with the current system.
I believe the right solution is a new profile system.

I just look at my wishes for future xoops some years ago
https://xoops.org/modules/mediawiki/index.php?title=Wishlist_for_XOOPS_30

Quote:

7. Modular Group system

7.1. all features in the current 2.0 system (*)

7.1.1. set modules and blocks permissions for a group

7.1.2. put a member to a group

7.1.3. find members for a group

7.2. set groups permissions for a module and its blocks

7.3. users can subscribe to and unsubscribe from a group. admin approval is needed (yes/no)

7.4. set password for a group. user that know the group password can join to the group. This feature is needed for role playing games websites.

7.5. Define head boy/girl for a group. this account have access to change all setting in that specific group

7.6. automatically join users to a group:

7.6.1. after register

7.6.2. after reach to greater than XX posts

7.6.3. after XX days elapsed from register date

7.6.4. after XX days elapsed from last login

7.7. automatically revoke users from a group:

7.7.1. after reach to less than XX post

7.7.2. after XX days elapsed from last login



126
irmtfan
Re: XOOPS 2.5.6 Final Release Issues
  • 2013/4/29 10:02

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


good.
I just wanted to sum up all bugs i discovered. (some bugs are old really )
IMO ability to login with email is more important than fast comment.
Anyway, I upgrade one little site without issue just FYI.
I think it would be good to create an upgrade package from full xoops2.5.6
(remove install, mainfile.php and add upgrade , ...)



127
irmtfan
Re: XOOPS 2.5.6 Final Release Issues
  • 2013/4/29 6:34

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Thank you Mamba for this release.

I just want to clear somethings:

1- it seems there is not any change in languages (definitions) but you remove extra spaces in many of them? is this right?

2- there are at least 4-5 bugs with their solutions in sf.net which are not implemented in this release.
I add them to persian release include some of the demanded features like:
- emaillogin hack (I still cannot understand why it is not in the core after 7 years)

- show real name instead of uname in who is online block

- change the default save a copy from your pm to yes.

- add local math captcha (convert numbers into words)

Quote:

- bug fix:http://sourceforge.net/p/xoops/bugs/1263/ xoModuleIcons32 cannot show the image link in class/smarty/xoops_plugins/compiler.xoModuleIcons32.php
- bug fix:http://sourceforge.net/p/xoops/bugs/1226/ disable CaricFoto js in class/textsanitizer/image/config.php
- bug fix:http://sourceforge.net/p/xoops/bugs/1225/ xoopsUserTheme is not regenerate if remember me was checked in login in include/common.php
- bug fix:http://sourceforge.net/p/xoops/bugs/1261/ footer is not loaded after header in header.php
- bug fix:http://sourceforge.net/p/xoops/bugs/1220/ xoops_getConfigOption('language') is wrongly return in include/functions.php around line 926
- show real name instead of uname in modules/system/blocks/system_blocks.php
- change default savecopy to true in modules/pm/pmlite.php
- increasing the uname size from 25 to 50 in kernel/user.php
- add EmailLogin hack in include/checklogin.php
- add convert numbers into words in english and persian in ./htdocs/language/english/locale.php,
./htdocs/language/persian/locale.php, ./htdocs/language/persian/locale.lang.php, ./htdocs/language/persian/locale.config.php


http://svn.code.sf.net/p/xoops/svn//XoopsLanguages/persian/core/trunk/

3- some bugs was reported but still unsolved:
1-http://sourceforge.net/p/xoops/bugs/1253/ XoopsForm classes cannot add anything to blockform (xoops256 and xoops26)
2-http://sourceforge.net/p/xoops/bugs/1234/ invalid tokens will be checked in an infinite loop (xoops256)
to reproduce go to a topic in forums and click on reply and write something then disconnect yourself (make sure you are disconnect completely) and connect again and then try to submit that post you wrote. it will stick on modules/newbb/post.php and will not reply until you logout and login again.

3-http://sourceforge.net/p/xoops/bugs/1221/ template set is not updated based on template file (xoops256)



128
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 10:18

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


Honestly im not good at presentation
I hope some people can help and implement this tutorial into the XOOPS 2.5.5 Standard Module Structure Guidelines with more images.

for example when i wrote It is the last column in table with truncate and ellipsis, it is necessary to have an image of the last column. (An image can show the user what happens more than 1000 lines of description.)

Also we need to add a full tutorial for xoops_version.php but I thought we have something similar somewhere.

edit:
i add one pic
Resized Image

so you can see what will be the outcome of a full div table.





129
irmtfan
Re: How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 7:18

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


I start by writing this tutorial.
but there are more information to add.
Finally we can have a good "how to write a standard module" document.
please add your notes.
especially designers can show us nice/easy ways.



130
irmtfan
How to write an standard module for xoops (div table, pagination , sort, order)
  • 2013/4/28 6:42

  • irmtfan

  • Module Developer

  • Posts: 3419

  • Since: 2003/12/7


In this tutorial we only want to show you the most standard way of writing XOOPS modules in a very basic and brief way to create standard tables by fetching data from database with the following goals:

1- Using Xoops Core API as much as possible.

2- Do not use any hard-code method in coding. for example:

2-1- Any HTML codes in PHP files. Or PHP codes in HTML files.

2-2- Any smarty variable that doing more than one job.

2-3- Any file like image, CSS, JavaScript that is called with a direct link in HTML, PHP files and any CSS codes being in HTML files instead of CSS.

2-4- Any self-defined functions in modules that are already defined in the core and doing the same thing. They just conflicts with each other.

2-5- Any direct access to database (eg: by using $GLOBALS["xoopsDB"]) and queries directly reading table information.

2-6- Any part of code that do not read/recognize the XoopsLocal class and local css/js/language files.

3- Find easiest, best, consistent and most advanced solutions for regular xoops coding obstacles with the following priority.

3-1- user side codes.
As a developer/debugger you should always try to find a user side solution. it is much better than manipulating php codes.

3-1-1- css: CSS is the best choice. You should always try to find a solution using css
pros: it is OS/Browser independent. even a very simple webmaster knows some css codes. It is local aware and multilingual.
cons: Nothing

3-1-2- html: It is good in combine with css

3-1-3- smarty: it is the third choice if you can not solve the issue using css/html
cons: it is not local aware. Xoops maybe use another template engine in the future.

3-1-4- javascript: it is the last resort in user side programming.
big disadvantage: user may turn the js off in the browser.

3-2- server side codes.

3-2-1- php: try to hack php codes only if you could not find a solution using css/html/smarty/js

3-2-2- mysql: altering/changing database is the last resort.

4- Make your module compatible with future xoops core even with xoops 2.6 as much as possible. So you have less trouble in the future.

5- build full div tables in html instead of using "table" "th" "tr" "td"

Also in this tutorial we will show you real examples from Publisher as an advance content module and Userlog as a logger module.

Now lets start step by step:

1- Before start writing any module it would be good to be more familiar with xoops modules structure. even if you are not a developer it will help you to understand the functionality of Xoops modules.

I recommend any xoops user to read Standard Module Structure
Guidelines
up to section 2.8.

It will help you to be more familiar with xoops system.

2- before write a module, start with some questions:

2-1- Is there any similar module that will work for me?
If you can find a similar module maybe you can contribute in developing/ bug fixing that module instead of writing a module from scratch. for example there is a lot of content, gallery and download modules in xoops which you can use and develop more or customize for your needs. honestly i see no mean to write any more content module in xoops.

2-2- Is there any similar functionality in other modules which i could borrow.

Maybe it seems odd but it will be good to announce in xoops.org forums about your futur plans for a module and your needs. Please explain what you want to do. Usually there are some modules with similar functionality which you can use in your module.

3- Start by writing xoops_version.php and mysql.sql

It will help you to find your module structure. so it would be clear for you what you want to do in the future.

Keep in mind that Xoops core always read your xoops_version.php before loading your module.

== START OF xoops_version.php ==
3-1- xoops_version.php
see an example of xoops_version.php from publisher.
So we can review them one by one:

3-1-1- $modversion:
It is a reserved name which xoops core find module data from. you should use only this name.

3-1-2- Module information: name,description, ...
- Note: many of the following will be shown in module > about page.
// Name of the module
$modversion['name'] = _MI_PUBLISHER_MD_NAME;

- You should get the name from language file.

// Version of the module
$modversion['version'] = 1.0;


- for versioning please pay attention that xoops core can just accept this format:
VERSION = YYYY.XX
So it will ignore any version after the second number after point.
so these versions are same for xoops core:
1.011
1.014
OR:
12.126
12.129

So if your module needs an upgrade you should change the first or second number after point.

// Brief description of the module
$modversion['description'] = _MI_PUBLISHER_MD_DESC;
// Author
$modversion['author'] = "Xuups.com";
// Credits - other people but not shown in about page
$modversion['credits'] = "w4z004, hsalazar, Mithrandir, fx2024, Ackbarr, Mariuss, Marco, Michiel, phppp, outch, Xvitry, Catzwolf, Shine, McDonald, trabis, Mowaffak, Bandit-x, Shiva";
// the arg of the help url, If your module have a help file
$modversion['help']  = 'page=help';
// License
$modversion['license'] = 'GNU GPL 2.0';
$modversion['license_url'] = "www.gnu.org/licenses/gpl-2.0.html/";
// If it’s an official module = 1 but usually it is not
$modversion['official'] = 0;


// Name of the directory that holds the module.
$modversion['dirname'] = basename(__DIR__);

- use basename(__DIR__) for better cloning.

// Path and name of the module’s logo
$logo_filename $modversion['dirname'] . "_logo.png";

if (
file_exists(XOOPS_ROOT_PATH "/modules/" $modversion['dirname'] . "/images/" $logo_filename)) {
    
$modversion['image'] = "images/{$logo_filename}";
} else {
    
$modversion['image'] = "images/module_logo.png";
}

- While you could simply add your logo like this:
// Path and name of the module’s logo
$modversion['image'] = "images/mymodule_logo.png";

The above from publisher is the best way for cloning.

3-1-3- module install/update/uninstall special scripts:
You can have some special scripts for your module to be execute on module install/update/uninstall.
// Install
$modversion['onInstall'] = 'include/install.php';
// Update
$modversion['onUpdate'] = 'include/update.php';


here you can define your module scripts which will be executed when Xoops core want to install/uninstall/update your module.
It is better to write all needed functions in one file and name it include/module.php to standardize it in all xoops modules.

so instead of what you see in publisher we recommend you to use one file like userlog module
$modversion['onUpdate'] = 'include/module.php';
$modversion['onUninstall'] = 'include/module.php';


for more information look at:
OnInstall/OnUpdate/OnUninstall functions tutorial

3-1-4- moduleadmin class:
You should use moduleadmin class framework in your module otherwise it will not work in xoops.
//about
$modversion['release_date']     = '2011/11/17';
$modversion['module_status']    = "RC";
$modversion['min_php']          = '5.2.0';
$modversion['min_xoops']        = '2.5.0';
$modversion['min_db']           = array('mysql'=>'5.0.7''mysqli'=>'5.0.7');
$modversion['min_admin']        = '1.1';
$modversion['dirmoduleadmin'] = '/Frameworks/moduleclasses/moduleadmin';
$modversion['icons16']        = '../../Frameworks/moduleclasses/icons/16';
$modversion['icons32']        = '../../Frameworks/moduleclasses/icons/32';


The above settings will be used in about/index page, as well as in menu.php file

All xoops modules should use moduleadmin class. so you can define icon paths here.

// Admin things
$modversion['hasAdmin'] = 1;
$modversion['adminindex'] = "admin/index.php";
$modversion['adminmenu'] = "admin/menu.php";
// If you want your module has a sub menu in system menu set it to 1
$modversion['system_menu'] = 1;


- admin/menu.php will be used by moduleadmin class to create your menu in admin area

3-1-5- tables:
If your module has tables you should define theme.
// Sql file (must contain sql generated by phpMyAdmin or phpPgAdmin)
$modversion['sqlfile']['mysql'] = "sql/mysql.sql";

// Tables created by sql file (without prefix!)
$i 0;
$modversion['tables'][$i] = 'mod_' $modversion['dirname'] . '_log';

$i++;
$modversion['tables'][$i] = 'mod_' $modversion['dirname'] . '_set';

$i++;
$modversion['tables'][$i] = 'mod_' $modversion['dirname'] . '_stats';


- as you can see we should follow this standard for module table names: "mod_" . $dirname . "_TABLE"

3-1-6- search functionality:
// Search
$modversion['hasSearch'] = 1;
$modversion['search']['file'] = "include/search.inc.php";
$modversion['search']['func'] = "publisher_search";

- The above is for search functionality:
It is better to write the 'func' name without $dirname to avoid hard-code:
$modversion['search']['func'] = $modversion["dirname"] . "_search";


You can find more information about xoops search system in this post:
https://xoops.org/modules/newbb/viewtopic.php?post_id=349697#forumpost349697

3-1-7- module links in mainmenu block:
// Menu
$modversion['hasMain'] = 1;


- shows your module has functionality in user side (in modules/MODULE/*.*)
It will show a link of your module in xoops 'mainmenu' block which show modules/MODULE/index.php page.

global $xoopsModule;
if (
is_object($xoopsModule) && $xoopsModule->getVar('dirname') == $modversion['dirname']) {
    global 
$xoopsModuleConfig$xoopsUser;
    
$isAdmin false;
    if (
is_object($xoopsUser)) {
        
$isAdmin $xoopsUser->isAdmin($xoopsModule->getVar('mid'));
    }
    
// Add the Submit new item button
    
$allowsubmit = (isset($xoopsModuleConfig['perm_submit']) && $xoopsModuleConfig['perm_submit'] == 1) ? true false;
    
$anonpost = (isset($xoopsModuleConfig['permissions_anon_post']) && $xoopsModuleConfig['permissions_anon_post'] == 1) ? true false;
    if (
$isAdmin || ($allowsubmit && (is_object($xoopsUser) || $anonpost))) {
        
$modversion['sub'][1]['name'] = _MI_PUBLISHER_SUB_SMNAME1;
        
$modversion['sub'][1]['url'] = "submit.php?op=add";
    }

    
// Add the Search button
    
$allowsearch = (isset($xoopsModuleConfig['perm_search']) && $xoopsModuleConfig['perm_search'] == 1) ? true false;
    if (
$allowsearch) {
        
$modversion['sub'][2]['name'] = _MI_PUBLISHER_SUB_SMNAME3;
        
$modversion['sub'][2]['url'] = "search.php";
    }
}
// Add the Archive button
$modversion['sub'][3]['name'] = _MI_PUBLISHER_SUB_ARCHIVE;
$modversion['sub'][3]['url'] = "archive.php";

- show sub links in the 'mainmenu' block.
As you can see you can prevent some users to some sub links like submit.php

3-1-8- module blocks:
If your module has a block you should define them here.
$modversion['blocks'][$i]['file'] = "views.php";
$modversion['blocks'][$i]['name'] = _MI_USERLOG_BLOCK_VIEWS;
$modversion['blocks'][$i]['description'] = _MI_USERLOG_BLOCK_VIEWS_DSC;
$modversion['blocks'][$i]['show_func'] = $modversion['dirname'] . "_views_show";
$modversion['blocks'][$i]['edit_func'] = $modversion['dirname'] . "_views_edit";
$modversion['blocks'][$i]['options'] = "10|0|1|-1|0|count|DESC";
$modversion['blocks'][$i]['template'] = $modversion['dirname'] . "_block_views.html";


- show that this module has block. you can see we can avoid hard-coded $dirname.
- The file for block functions (show_func and edit_func) will be located in modules/MODULE/blocks
eg: modules/MODULE/blocks/views.php
- show_func: is the function that will be parsed when the block is visible.
- edit_func: is the function that just add a form in Blocks Administration (modules/system/admin.php?fct=blocksadmin&op=edit&bid=BLOCK_ID) for 'options' field.
a module block may not have edit_func if it dont need any extra options.
- options: are default options only will be stored in module install. (Note: So in upgrade they will not be changed in database if you change them here!!!)
- template: the template for this block located in modules/MODULE/templates/blocks
eg: modules/MODULE/templates/blocks/userlog_block_views.html

3-1-9- module templates:
Your module should use templates for user and admin side.
// Templates - if you dont define 'type' it will be 'module' | '' -> templates
$i 0;
$modversion['templates'][$i]['file'] = $modversion['dirname'] . '_admin_sets.html';
$modversion['templates'][$i]['type'] = 'admin'// $type = 'blocks' -> templates/blocks , 'admin' -> templates/admin , 'module' | '' -> templates
$modversion['templates'][$i]['description'] = 'list of userlog setting';

- Templates of your module will show a nice and formatted data in user side.
- Try always use one template for one page/section of your module. More templates means less hard-code.
- Note that you should avoid any direct php output like echo, print_r and ...
- Template types are: 'module' (default), 'admin', 'blocks' (if you dont define it will be 'module')
- In the current xoops 2.5.6 'description' is not used anywhere so you dont need to add a language definition.

3-1-10- module configurations:
Xoops core use 3 tables to save core and module configs:
_config
_configcategory
_configoption

So you dont need to have extra tables for your module configurations.

These tables used for storing module and core configs in database.
configs can be changed in admin > Preferences > MODULE:
xoops256/modules/system/admin.php?fct=preferences&op=showmod&mod=MID

and configs are in modules/MODULE/xoops_version.php
example:
$i=0;
$modversion['config'][$i]['name'] = 'status';
$modversion['config'][$i]['title'] = '_MI_USERLOG_STATUS';
$modversion['config'][$i]['description'] = '_MI_USERLOG_STATUS_DSC';
$modversion['config'][$i]['formtype'] = 'select';
$modversion['config'][$i]['valuetype'] = 'int';
$modversion['config'][$i]['default'] = 1;
$modversion['config'][$i]['options'] = array(_MI_USERLOG_ACTIVE => 1_MI_USERLOG_IDLE => 0);

$i++;
$modversion['config'][$i]['name'] = 'postlog';
$modversion['config'][$i]['title'] = '_MI_USERLOG_POSTLOG';
$modversion['config'][$i]['description'] = '_MI_USERLOG_POSTLOG_DSC';
$modversion['config'][$i]['formtype'] = 'yesno';
$modversion['config'][$i]['valuetype'] = 'int';
$modversion['config'][$i]['default'] = 1;
$modversion['config'][$i]['options'] = array();

_config table:
the below information will be stored in config table:
conf_id: auto generate id
conf_modid: module id
conf_catid: 0 not used in 2.5x series but reserve for 2.6
conf_name: $modversion['config'][$i]['name']
conf_title: $modversion['config'][$i]['title']
conf_value: The value selected by user from $modversion['config'][$i]['options']. The default is: $modversion['config'][$i]['default'] which will be stored once the module is installed.
conf_desc: $modversion['config'][$i]['description']
conf_formtype: $modversion['config'][$i]['formtype']
conf_valuetype: $modversion['config'][$i]['valuetype']
conf_order: $i : number of config in the $modversion['config'] array

A side note about changing conf_value:
the conf_value will be changed only by:
a - install a fresh module which $modversion['config'][$i]['default'] will be stored(as i described above).
b - select by user (as i described above)
c - In update when formtype or valuetype was changed in xoops_version.php $modversion['config'][$i]['default'] will be stored.
A developer may decide to change the value by this trick!!!

_configcategory table:
In 2.5.x only used by core and not used for modules. reserve for xoops 2.6

_configoption table:
information comes from $modversion['config'][$i]['options'] will be stored in this table.
if empty($modversion['config'][$i]['options']) => nothing will be stored in this table.
As an example for this option:
$modversion['config'][$i]['options'] = array(_MI_USERLOG_ACTIVE => 1_MI_USERLOG_IDLE => 0);


Below information will be stored:
confop_id : auto generate option id
confop_name: _MI_USERLOG_ACTIVE (will be parsed to user own language)
confop_value: 1
conf_id: the linked id in config table.

Note: options only will be stored in install and update process.

== END OF xoops_version.php ==

3-2- mysql.sql
Also if your module have some kind of data you need to define your database tables.
Currently the standard for table names is this:
mod_MODULE-DIRNAME-IN-LOWERCASE_MODULE-TABLENAME

for example look at userlog mysql.sql

here you can find tables:
mod_userlog_log
mod_userlog_set
mod_userlog_stats

Then you should create a class for each table and in the future any access to database will be done by using those classes.
see in userlog one class is defined for each table:
userlog/class/log.php for mod_userlog_log table
userlog/class/setting.php for mod_userlog_set table
userlog/class/stats.php for mod_userlog_stats table

Now lets start in depth with an example from publisher module.
we want to see how we can write a class for table publisher_categories in sql file.
at first you can see the table fields and structure in sql file:
CREATE TABLE `PUBLISHER_categories` ( 
  `
categoryidint(11NOT NULL auto_increment
  `
parentidint(11NOT NULL default '0'
  `
namevarchar(100NOT NULL default ''
  `
descriptiontext NOT NULL
  `
imagevarchar(255NOT NULL default ''
  `
totalint(11NOT NULL default '0'
  `
weightint(11NOT NULL default '1'
  `
createdint(11NOT NULL default '1033141070'
  `
templatevarchar(255NOT NULL default ''
  `
headertext NOT NULL
  `
meta_keywordstext NOT NULL
  `
meta_descriptiontext NOT NULL
  `
short_urlvarchar(255NOT NULL default ''
  `
moderatorint(6NOT NULL default '0'
  
PRIMARY KEY (`categoryid`), 
  
KEY parentid (parentid
ENGINE=MyISAM;

then see how to define the class.
all classes in modules for working with database tables, extend XoopsObject class located in XOOPSCORE/kernel/object.php. therefore XoopsObject will be the heart of any module for accessing to database and run any query. (read/write/stats)
The class filename in publisher is category.php

you can see how to define your fields and their types:
class PublisherCategory extends XoopsObject 

    
/** 
     * @var PublisherPublisher 
     * @access public 
     */ 
    
public $publisher null

    
/** 
     * @var array 
     */ 
    
public $_categoryPath false

    
/** 
     * constructor 
     */ 
    
public function __construct() 
    { 
        
$this->publisher PublisherPublisher::getInstance(); 
        
$this->initVar("categoryid"XOBJ_DTYPE_INTnullfalse); 
        
$this->initVar("parentid"XOBJ_DTYPE_INTnullfalse); 
        
$this->initVar("name"XOBJ_DTYPE_TXTBOXnulltrue100); 
        
$this->initVar("description"XOBJ_DTYPE_TXTAREAnullfalse255); 
        
$this->initVar("image"XOBJ_DTYPE_TXTBOXnullfalse255); 
        
$this->initVar("total"XOBJ_DTYPE_INT1false); 
        
$this->initVar("weight"XOBJ_DTYPE_INT1false); 
        
$this->initVar("created"XOBJ_DTYPE_INTnullfalse); 
        
$this->initVar("template"XOBJ_DTYPE_TXTBOXnullfalse255); 
        
$this->initVar("header"XOBJ_DTYPE_TXTAREAnullfalse); 
        
$this->initVar("meta_keywords"XOBJ_DTYPE_TXTAREAnullfalse); 
        
$this->initVar("meta_description"XOBJ_DTYPE_TXTAREAnullfalse); 
        
$this->initVar("short_url"XOBJ_DTYPE_TXTBOXnullfalse255); 
        
$this->initVar("moderator"XOBJ_DTYPE_INTnullfalse0); 
        
//not persistent values 
        
$this->initVar("itemcount"XOBJ_DTYPE_INT0false); 
        
$this->initVar('last_itemid'XOBJ_DTYPE_INT); 
        
$this->initVar('last_title_link'XOBJ_DTYPE_TXTBOX); 
        
$this->initVar("dohtml"XOBJ_DTYPE_INT1false); 
    }


Then you should write another class by extending XoopsPersistableObjectHandler for access to table:
/** 
 * Categories handler class. 
 * This class is responsible for providing data access mechanisms to the data source
 * of Category class objects. 
 * 
 * @author  marcan <marcan@notrevie.ca> 
 * @package Publisher 
 */ 
class PublisherCategoryHandler extends XoopsPersistableObjectHandler 

    
/** 
     * @var PublisherPublisher 
     * @access public 
     */ 
    
public $publisher null

    
/** 
     * @param null|object $db 
     */ 
    
public function __construct(&$db
    { 
        
$this->publisher PublisherPublisher::getInstance(); 
        
parent::__construct($db"publisher_categories"'PublisherCategory'"categoryid""name");
    }

publisher_categories is your table name
PublisherCategory is your Object class name.
categoryid is your table increment key field
name is your table second key field.

You need to read XoopsObject class functions completely and carefully. In this tutorial we will use this class a lot.

As you can see we have an instance of publisher helper object to use it anywhere we need that helper.
for example to access another table in this handler. (eg: in joining purposes)
$this->publisher->getHandler('item')->table

means publisher_items table.

4- write a common.php file for your module.

In this file you will write basic needs like loading Core/Module functions/classes and definitions for future using in all of your other functions in module.
see an example in publisher/include/common.php.

defined("XOOPS_ROOT_PATH") or die("XOOPS root path not defined");

define("PUBLISHER_DIRNAME"basename(dirname(__DIR__)));
define("PUBLISHER_URL"XOOPS_URL '/modules/' PUBLISHER_DIRNAME);
define("PUBLISHER_IMAGES_URL"PUBLISHER_URL '/images');
define("PUBLISHER_ADMIN_URL"PUBLISHER_URL '/admin');
define("PUBLISHER_UPLOADS_URL"XOOPS_URL '/uploads/' PUBLISHER_DIRNAME);
define("PUBLISHER_ROOT_PATH"XOOPS_ROOT_PATH '/modules/' PUBLISHER_DIRNAME);
define("PUBLISHER_UPLOADS_PATH"XOOPS_ROOT_PATH '/uploads/' PUBLISHER_DIRNAME);

xoops_loadLanguage('common'PUBLISHER_DIRNAME);

include_once 
PUBLISHER_ROOT_PATH '/include/functions.php';
include_once 
PUBLISHER_ROOT_PATH '/include/constants.php';
include_once 
PUBLISHER_ROOT_PATH '/include/seo_functions.php';
include_once 
PUBLISHER_ROOT_PATH '/class/metagen.php';
include_once 
PUBLISHER_ROOT_PATH '/class/session.php';
include_once 
PUBLISHER_ROOT_PATH '/class/publisher.php';
include_once 
PUBLISHER_ROOT_PATH '/class/request.php';

$debug false;
$publisher PublisherPublisher::getInstance($debug);


see how it defines all needs.

5- write a module class for your module.

It will help you to put general functions in that class for accessing your module information. generally you can name the file the same as your module dirname.
for example in publisher it is publisher/class/publisher.php:
defined("XOOPS_ROOT_PATH") or die("XOOPS root path not defined");

class 
PublisherPublisher
{
    var 
$dirname;
    var 
$module;
    var 
$handler;
    var 
$config;
    var 
$debug;
    var 
$debugArray = array();

    protected function 
__construct($debug)
    {
        
$this->debug $debug;
        
$this->dirname =  basename(dirname(__DIR__));
    }

    static function &
getInstance($debug false)
    {
        static 
$instance false;
        if (!
$instance) {
            
$instance = new self($debug);
        }
        return 
$instance;
    }

    function &
getModule()
    {
        if (
$this->module == null) {
            
$this->initModule();
        }
        return 
$this->module;
    }

    function 
getConfig($name null)
    {
        if (
$this->config == null) {
            
$this->initConfig();
        }
        if (!
$name) {
            
$this->addLog("Getting all config");
            return 
$this->config;
        }
        if (!isset(
$this->config[$name])) {
            
$this->addLog("ERROR :: CONFIG '{$name}' does not exist");
            return 
null;
        }
        
$this->addLog("Getting config '{$name}' : " $this->config[$name]);
        return 
$this->config[$name];
    }

    function 
setConfig($name null$value null)
    {
        if (
$this->config == null) {
            
$this->initConfig();
        }
        
$this->config[$name] = $value;
        
$this->addLog("Setting config '{$name}' : " $this->config[$name]);
        return 
$this->config[$name];
    }

    function &
getHandler($name)
    {
        if (!isset(
$this->handler[$name '_handler'])) {
            
$this->initHandler($name);
        }
        
$this->addLog("Getting handler '{$name}'");
        return 
$this->handler[$name '_handler'];
    }

    function 
initModule()
    {
        global 
$xoopsModule;
        if (isset(
$xoopsModule) && is_object($xoopsModule) && $xoopsModule->getVar('dirname') == $this->dirname) {
            
$this->module $xoopsModule;
        } else {
            
$hModule xoops_gethandler('module');
            
$this->module $hModule->getByDirname($this->dirname);
        }
        
$this->addLog('INIT MODULE');
    }

    function 
initConfig()
    {
        
$this->addLog('INIT CONFIG');
        
$hModConfig xoops_gethandler('config');
        
$this->config $hModConfig->getConfigsByCat(0$this->getModule()->getVar('mid'));
    }

    function 
initHandler($name)
    {
        
$this->addLog('INIT ' $name ' HANDLER');
        
$this->handler[$name '_handler'] = xoops_getModuleHandler($name$this->dirname);
    }

    function 
addLog($log)
    {
        if (
$this->debug) {
            if (
is_object($GLOBALS['xoopsLogger'])) {
                
$GLOBALS['xoopsLogger']->addExtra($this->module->name(), $log);
            }
        }
    }
}

as you can see it is a very basic class just for initializing module handler, config, ...
but it will help a lot in other modules.
back to common.php you can see the developer create an object like this:
$debug false;
$publisher PublisherPublisher::getInstance($debug);

therefore in all other functions you can use it with ease.
eg:
$publisher->getHandler('category')->delete($categoryObj);


I repeat, it will help you a lot.
In xoops 26 there is a class called helper that will do the same job so by doing this in xoops255 and xoops256 modules you will be ready for the helper class.

Finally look at userlog/class/userlog.php as an another example.

6- deal with all request methods like $_GET, $_POST, $REQUEST, $_COOKIE,

prior to xoops26 each developer should deal with them. but in xoops 2.6 there is XoopsRequest class.
So what to do in xoops255 and xoops256?
there is a very simple solution thanks to core developer Trabis. in publisher there is a PublisherRequest class which you can copy/paste in your module with ease.
It is located at publisher/class/request.php

do these steps:
a) copy publisher/class/request.php to YOUR_MODULE/class/request.php

b) open the file and replace all "Publisher" with "Yourmodule"
pay attention to match case.
for example see in userlog/class/request.php

we will use request class in the future.

7- Create an standard table.

now back to our main deal. we want to create an standard table by fetching data from database and implement user side facilities like search, sort, order, pagination easily.

lets look at userlog/admin/logs.php because this module want to show a lot of data in a nice and easy format to read. Im sure you will see all of your issues and find their solutions in this example.

7-1- Where do we start ?
That is the question. what we want to show to the user when he click on the URI without any entry?
You should decide about these entries:
$startentry UserlogRequest::getInt('startentry',0);
$limitentry UserlogRequest::getInt('limitentry',$Userlog->getConfig("logs_perpage"));
$sortentry UserlogRequest::getString('sortentry','log_id');
$orderentry UserlogRequest::getString('orderentry','DESC');

As you can see by using the request class, we have an easy task to do. we just should get the data based on their types.
the first parameter is the name of entry in the URI.
the second parameter is the default value when entry is empty in URI

So for a given URI like this:
userlog/admin/logs.php?startentry=100&&limitentry=10&sortentry=log_time&orderentry=ASC

It will get the data from URI for all above entries.

clas request also can accept a third parameter for defining the request_method. the default for request method is $_REQUEST
$opentry UserlogRequest::getString('op''''post');


so in the above example we force it to get 'op' entry from $_POST method.

7-2- Criteria.
Any access to database needs a criteria. fortunately xoops core has a criteria class located in XOOPSCORE/class/criteria.php
you can use this class to create most usual criteria. So you can use this criteria to get data from database with START, LIMIT, SORT and ORDER

$logs $Userlog->getHandler('log')->getLogs($limitentry,$startentry,$criteria,$sortentry,$orderentry ,nullfalse);


and we told you that all access to database will be done thru module class. so look at function getLogs in userlog/class/log.php

$criteria = new CriteriaCompo();
        if (!empty(
$otherCriteria)) {
            
$criteria->add($otherCriteria);
        }
        
$criteria->setLimit($limit);
        
$criteria->setStart($start);
        
$criteria->setSort($sort);
        
$criteria->setOrder($order);
        
$ret $this->getAll($criteria$fields$asObject$id_as_key);


you can see how you can use criteria class.
At first you just need to define it.
then use some core functions like setSort.

The getAll function is the most important xoops function in working with database. you just need to use it to read any data.

7-3- Save entries and assign them to template.
we need to save all entries in the url for future usage in user navigation.
$GLOBALS['xoopsTpl']->assign('startentry'$startentry);
$GLOBALS['xoopsTpl']->assign('limitentry'$limitentry);
$GLOBALS['xoopsTpl']->assign('sortentry'$sortentry);
$GLOBALS['xoopsTpl']->assign('orderentry'$orderentry);


Also look at this:
// create query entry
$query_entry "&engine=" $engine "&limitentry=" $limitentry "&sortentry=" $sortentry "&orderentry=" $orderentry;

then some lines after that:
// query entry
$GLOBALS['xoopsTpl']->assign('query_entry', !empty($query_entry) ? $query_entry '');


So we can use it in template as an smarty variable.

7-4- page navigation:
At first you should have total count of data.
$totalLogs $Userlog->getHandler('log')->getLogsCount($criteria);

getLogsCount is a function that just count based on Xoops Core API getCount
so you can use getCount($criteria) in your module.
Then you should use the pagenav class to create a page navigation and assign it to template.
// pagenav to template
$pagenav = new XoopsPageNav($totalLogs$limitentry$startentry'startentry'$query_entry . (!empty($query_page) ? "&" $query_page ''));
$GLOBALS['xoopsTpl']->assign("pagenav", !empty($pagenav) ? $pagenav->renderNav() : '');


7-5- page navigation, sort, order in template.

look at userlog/templates/userlog_admin_logs.html

<{foreach item=title key=header from=$headers}>
            <
div title="<{$title}>" class="truncate width<{if $header == "admin" || $header == "pageadmin" || $header == "log_id" || $header == "uid" || $header == "item_name" || $header == "item_id"}>1<{else}><{$widthC}><{/if}> floatleft center">
                <
class="ui-corner-all tooltip" title="<{$title}>" href="logs.php?limitentry=<{$limitentry}>&sortentry=<{$header}><{if $query_page}>&<{$query_page}><{/if}><{if $sortentry eq $header}>&orderentry=<{if $orderentry eq 'DESC'}>ASC<{else}>DESC<{/if}><{/if}> " alt="<{$title}>"><{if $sortentry eq $header}><img src="<{if $orderentry eq 'DESC'}><{xoModuleIcons16 DESC.png}><{else}><{xoModuleIcons16 ASC.png}><{/if}>"/><{/if}><{$title}></a>
            </
div>
        <{/foreach}>


you just need to know some smarty.
As you can see for sort and order i use this if:
<{if $sortentry eq $header}>&orderentry=<{if $orderentry eq 'DESC'}>ASC<{else}>DESC<{/if}><{/if}>


it will find if user click on the header as sortentry. then if the order was not defined or it was ASC it will be DESC otherwise it will be ASC.

for showing an arrow. you just need another same if.
<{if $sortentry eq $header}><img src="<{if $orderentry eq 'DESC'}><{xoModuleIcons16 DESC.png}><{else}><{xoModuleIcons16 ASC.png}><{/if}>"/><{/if}>


pay attention that we will save the query_entry in every click.

for page navigation you just need to put the smart var any where you like
<{$pagenav}>


7-6- full div tables.

Only one question should be answered by you before writing full div table.
a) Is each row has a same height?
OR
b) every row should have the height of the tallest column in the table.

In userlog my answer is (a) so it is much simpler. for (b) you should look at newbb full div template set in newbb_thread.html template.

look at userlog/templates/userlog_admin_sets.html (userlog/templates/userlog_admin_logs.html used the same method but has more functionality)


userlog_admin_sets.html is more understandable for you.
<div class="head border x-small">
        <
div class="width1 floatleft center"><{$smarty.const._AM_USERLOG_SET_ID}></div>
        <
div class="width5 floatleft center"><{$smarty.const._AM_USERLOG_SET_ACTIVE}></div>
        <
div class="width10 floatleft center"><{$smarty.const._AM_USERLOG_SET_NAME}></div>
        <
div class="width5 floatleft center"><{$smarty.const._AM_USERLOG_SET_LOGBY}></div>
        <
div class="width5 floatleft center"><{$smarty.const._AM_USERLOG_SET_UNIQUE_ID}></div>
        <
div class="width40 floatleft center"><{$smarty.const._AM_USERLOG_SET_OPTIONS}></div>
        <
div class="width20 floatleft center"><{$smarty.const._AM_USERLOG_SET_SCOPE}></div>
        <
div class="truncate center"><{$smarty.const._EDIT}>|<{$smarty.const._DELETE}>|<{$smarty.const._AM_USERLOG_ADMENU_LOGS}></div>
        <
div class="clear"></div>
    </
div>


so for each row (here header row) you just need to define 2 nested divs.
the first div indicate the row:
<div class="head border x-small">

the second divs are columns.

In any column you should define a width and location (floatleft) until the last column. (floatleft can be defined in RTL languages as floatleft {float: right;})

width1, width5, width40 are defined width in xoops.css
Note: always try to use xoops.css styles at first. it will be consistent in all xoops and modules and all future versions.

In the last column we dont need to fix the width but we can use a truncate style to fit any long sentence to the row.
see in userlog/templates/css/style.css :
.truncate {
    
white-spacenowrap;
    
overflowhidden;
}
.
ellipsis {
    
text-overflowellipsis;  // IE 6+, FF 7+, Op 11+, Saf 1.3+, Chr 1+
}


Finally this line is very important:
<div class="clear"></div>


It will indicate the end of one row.
After finishing header, for other rows you can use a for loop
<{foreach item=set from=$sets}>
    <
div class="<{cycle values='even,odd'}> <{if $set.active eq 0}> deactive <{/if}> bold border">
        <
div class="width1 floatleft center"><{$set.set_id}></div>
        <
div class="width5 floatleft center"><{if $set.active eq 0}><{$smarty.const._MI_USERLOG_IDLE}><{else}><{$smarty.const._MI_USERLOG_ACTIVE}><{/if}></div>        
        <
div class="width10 floatleft center"><{$set.name}></div>
        <
div class="width5 floatleft center"><{$set.logby}></div>
        <
div class="width5 floatleft center"><{$set.unique_id}></div>
        <
div title="<{$set.options}>" class="width40 floatleft left"><{$set.options}></div>
        <
div title="<{$set.scope}>" class="width20 floatleft left"><{$set.scope}></div>
        <
div class="truncate ellipsis left">
            <
a href="setting.php?set_id=<{$set.set_id}>" title="<{$smarty.const._EDIT}>"><img src="<{xoModuleIcons16 edit.png}>" alt="<{$smarty.const._EDIT}>" title="<{$smarty.const._EDIT}>" /></a>
            |
            <
a href="setting.php?op=del&set_id=<{$set.set_id}>" title="<{$smarty.const._DELETE}>"><img src="<{xoModuleIcons16 delete.png}>" alt="<{$smarty.const._DELETE}>" title="<{$smarty.const._DELETE}>" /></a>
            |
            <
a href=
            <{if 
$set.unique_id eq 0}>
                
"logs.php"
            
<{elseif $set.logby eq $smarty.const._AM_USERLOG_UID}>
                
"logs.php?options[uid]=<{$set.unique_id}>"
            
<{elseif $set.logby eq $smarty.const._AM_USERLOG_SET_GID}>
                
"logs.php?options[groups]=g<{$set.unique_id}>"
            
<{elseif $set.logby eq $smarty.const._AM_USERLOG_SET_IP}>
                
"logs.php?options[user_ip]=<{$set.unique_id}>"
            
<{/if}>
                
title="<{$smarty.const._AM_USERLOG_ADMENU_LOGS}> - <{$set.logby}>=<{$set.unique_id}>" alt="<{$smarty.const._AM_USERLOG_ADMENU_LOGS}> - <{$set.logby}>=<{$set.unique_id}>" ><{$smarty.const._AM_USERLOG_ADMENU_LOGS}> - <{$set.logby}>=<{$set.unique_id}></a>            
        </
div>
        <
div class="clear"></div>
    </
div>
    <{/foreach}>


as you can see we can use ellipsis style in the last column to show a nice three dots (...) when the sentence is not fit.

A general note for theme/template designers: define an alignment for each column/row. It will be vitally important for RTL languages. so you can see in english it would be left { text-align: left;} but in persian it is left { text-align: right;}
the center show that it will be center in LTR and RTL.




TopTop
« 1 ... 10 11 12 (13) 14 15 16 ... 284 »



Login

Who's Online

101 user(s) are online (85 user(s) are browsing Support Forums)


Members: 0


Guests: 101


more...

Donat-O-Meter

Stats
Goal: $100.00
Due Date: May 31
Gross Amount: $0.00
Net Balance: $0.00
Left to go: $100.00
Make donations with PayPal!

Latest GitHub Commits