├── .travis.yml
├── README.md
├── composer.json
├── phprelease.ini
├── phpunit.xml
├── src
└── WebUI
│ ├── Components
│ ├── Breadcrumbs.php
│ ├── Menu
│ │ ├── IdentityFinder.php
│ │ ├── Menu.php
│ │ ├── MenuFolder.php
│ │ ├── MenuItem.php
│ │ ├── MenuItemCollection.php
│ │ └── MenuItemInterface.php
│ ├── Pager
│ │ ├── BasePager.php
│ │ ├── Bootstrap2Pager.php
│ │ ├── Bootstrap3Pager.php
│ │ ├── BootstrapPager.php
│ │ └── Pager.php
│ └── React
│ │ └── ReactComponent.php
│ └── Core
│ ├── Div.php
│ ├── Element.php
│ └── Span.php
└── tests
└── WebUI
├── Components
├── BreadcrumbsTest.php
├── Menu
│ └── MenuTest.php
├── PagerTest.php
└── React
│ └── ReactComponentTest.php
└── Core
└── ElementTest.php
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.4
4 | - 5.5
5 | - 5.6
6 |
7 | before_script:
8 | - phpenv rehash
9 | - composer self-update
10 | - composer require satooshi/php-coveralls "^1" --no-update --dev
11 | - composer install
12 |
13 | script:
14 | - phpunit
15 |
16 | after_script:
17 | - php vendor/bin/coveralls -v
18 |
19 | cache:
20 | - vendor
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WebUI
2 | =========================
3 |
4 | WebUI aims to provide a PHP interface to build HTML components with microdata.
5 |
6 |
7 |
8 | Synopsis
9 | -------------------------
10 |
11 |
12 | ```php
13 | $el = new Element('span');
14 | $el->append('>');
15 | $el->addClass('separator');
16 |
17 | $breadcrumbs = new Breadcrumbs;
18 |
19 | $breadcrumbs->setSeparatorElement($el);
20 |
21 | $breadcrumbs->appendLink('Home', '/', 'The Home Page');
22 | $breadcrumbs->appendLink('Product', '/', 'All Products');
23 | $html = $breadcrumbs->render();
24 | ```
25 |
26 | And we will get:
27 |
28 | ```html
29 |
100 | if (!$this->label) {
101 | throw new Exception('Missing menu label');
102 | }
103 |
104 | // create label with tag "a"
105 | $a = new Element('a', $this->linkAttributes);
106 | $a->append($this->label);
107 | $this->append($a);
108 |
109 | $this->append($this->menuItemCollection);
110 |
111 | return parent::render($attrs);
112 | }
113 |
114 | }
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/WebUI/Components/Menu/MenuItem.php:
--------------------------------------------------------------------------------
1 | '#' );
16 |
17 | protected $identity;
18 |
19 | public function __construct($label, array $linkAttributes = null, array $attributes = array(), $identity = null)
20 | {
21 | $this->setLabel($label);
22 | if ($linkAttributes) {
23 | $this->setLinkAttributes($linkAttributes);
24 | }
25 | parent::__construct('li', array_merge(array(
26 | "role" => "presentation",
27 | "itemprop" => "itemListElement",
28 | "itemscope" => NULL,
29 | ), $attributes));
30 | $this->setIdentity( $identity ?: uniqid('menuItem') );
31 | }
32 |
33 | public function setIdentity($identity)
34 | {
35 | $this->identity = $identity;
36 | }
37 |
38 | public function getIdentity()
39 | {
40 | return $this->identity;
41 | }
42 |
43 | public function setLabel($label)
44 | {
45 | $this->label = $label;
46 | }
47 |
48 | public function setLinkAttributes(array $attributes)
49 | {
50 | $this->linkAttributes = $attributes;
51 | }
52 |
53 | public function setLink($label, array $attributes = array()) {
54 | $this->label = $label;
55 | $this->linkAttributes = $attributes;
56 | }
57 |
58 | public function render($attributes = array())
59 | {
60 | if (!$this->label) {
61 | throw new Exception('Missing menu label');
62 | }
63 |
64 | // create label with tag "a"
65 | $a = new Element('a', $this->linkAttributes);
66 | $a->setAttributeValue("role","menuitem");
67 | $a->append($this->label);
68 | $this->append($a);
69 | return parent::render($attributes);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/WebUI/Components/Menu/MenuItemCollection.php:
--------------------------------------------------------------------------------
1 | "menu",
21 | "itemscope" => NULL,
22 | "class" => "nav",
23 | "itemtype" => "http://schema.org/ItemList",
24 | ), $attributes));
25 |
26 | $this->setIdentity( $identity ?: crc32(microtime()) );
27 | }
28 |
29 | public function setIdentity($identity)
30 | {
31 | $this->identity = $identity;
32 | }
33 |
34 | public function getIdentity()
35 | {
36 | return $this->identity;
37 | }
38 |
39 | public function render($attrs = array())
40 | {
41 | $this->setAttributeValue('role', 'menu');
42 | $this->setAttributes(array(
43 | 'itemscope' => null,
44 | 'itemtype' => "http://schema.org/ItemList",
45 | ));
46 |
47 | foreach ($this->menuItems as $item) {
48 | $this->append($item);
49 | }
50 | return Element::render($attrs);
51 | }
52 |
53 | public function appendLink($label, array $linkAttributes = array(), array $attributes = array()) {
54 | $item = new MenuItem($label, $attributes);
55 | $item->setLinkAttributes($linkAttributes);
56 | $this->addMenuItem($item);
57 | return $item;
58 | }
59 |
60 | public function appendFolder($label, array $attributes = array(), array $config = array())
61 | {
62 | $folder = self::createFolder($label, $attributes, $config);
63 | $this->addMenuItem($folder);
64 | return $folder;
65 | }
66 |
67 |
68 | static public function createItem($label, array $linkAttributes = array(), array $attributes = array())
69 | {
70 | $item = new MenuItem($label, $attributes);
71 | $item->setLinkAttributes($linkAttributes);
72 | return $item;
73 | }
74 |
75 | static public function createFolder($label, array $attributes = array(), array $config = array())
76 | {
77 | $folder = new MenuFolder($attributes);
78 | if (isset($config['icon_class'])) {
79 | $label = '
' . $label;
80 | }
81 | $folder->setLabel($label);
82 | return $folder;
83 | }
84 |
85 |
86 |
87 | public function addMenuItem(MenuItemInterface $item)
88 | {
89 | $this->menuItems[] = $item;
90 | }
91 |
92 | public function getMenuItemByIndex($index)
93 | {
94 | if (isset($this->menuItems[ $index ])) {
95 | return $this->menuItems[ $index ];
96 | }
97 | }
98 |
99 | public function removeMenuItemByIndex($index)
100 | {
101 | return array_splice($this->menuItems, $index, 1);
102 | }
103 |
104 | public function getIterator()
105 | {
106 | return new ArrayIterator($this->menuItems); // array
107 | }
108 |
109 | public function count()
110 | {
111 | return count($this->menuItems);
112 | }
113 |
114 | public function findById($identity)
115 | {
116 | foreach( $this->menuItems as $item ) {
117 | if ($item instanceof IdentityFinder) {
118 | if ($result = $item->findById($identity)) {
119 | return $result;
120 | }
121 | } else if ( $item->getIdentity() === $identity ) {
122 | return $item;
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/WebUI/Components/Menu/MenuItemInterface.php:
--------------------------------------------------------------------------------
1 | 0 ? ceil($numberOfTotalItems / $pageSize) : 1;
9 | }
10 | }
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/WebUI/Components/Pager/Bootstrap2Pager.php:
--------------------------------------------------------------------------------
1 | firstText = _('Page.First');
31 | $this->lastText = _('Page.Last');
32 | $this->nextText = _('Page.Next');
33 | $this->prevText = _('Page.Previous');
34 | $this->currentPage = $currentPage;
35 | $this->totalPages = $totalPages;
36 | }
37 |
38 |
39 | public function setFirstPageText($text)
40 | {
41 | $this->firstText = $text;
42 | }
43 |
44 | public function setLastPageText($text)
45 | {
46 | $this->lastText = $text;
47 | }
48 |
49 | public function setNextPagetext($text)
50 | {
51 | $this->nextText = $text;
52 | }
53 |
54 | public function setPrevPageText($text)
55 | {
56 | $this->prevText = $text;
57 | }
58 |
59 | public function addClass($class)
60 | {
61 | $this->wrapperClass[] = $class;
62 | }
63 |
64 | public function mergeQuery( $orig_params , $params = array() )
65 | {
66 | $params = array_merge( $orig_params , $params );
67 |
68 | return '?' . http_build_query( $params );
69 | }
70 |
71 | public function renderLink( $num , $text = null , $moreclass = "" , $disabled = false , $active = false )
72 | {
73 | if ( $text == null )
74 | $text = $num;
75 |
76 | if ( $disabled )
77 | return $this->renderDisabledLink( $text , $moreclass );
78 |
79 | $args = array_merge( $_GET , $_POST );
80 | $href = $this->mergeQuery( $args , array( "page" => $num ) );
81 | $liClass = '';
82 | if ( $active ) {
83 | $liClass = 'active';
84 | }
85 | return <<
87 | EOF;
88 | }
89 |
90 | protected function renderDisabledLink( $text , $moreclass = "" )
91 | {
92 | return <<
94 | EOF;
95 | }
96 |
97 | public function __toString()
98 | {
99 | return $this->render();
100 | }
101 |
102 | public function render()
103 | {
104 | $cur = $this->currentPage;
105 | $total_pages = $this->totalPages;
106 |
107 | if ($this->whenOverflow && $this->totalPages <= 1) {
108 | return "";
109 | }
110 |
111 | $pagenum_start = $cur > $this->rangeLimit ? $cur - $this->rangeLimit : 1 ;
112 | $pagenum_end = $cur + $this->rangeLimit < $total_pages ? $cur + $this->rangeLimit : $total_pages;
113 |
114 | $output = "";
115 | $output .= '';
116 | $output .= '
';
117 |
118 | if ($this->showNavigator) {
119 | if ( $cur > 1 )
120 | $output .= $this->renderLink( 1 , $this->firstText , 'pager-first' , $cur == 1 );
121 |
122 | if ( $cur > 1 )
123 | $output .= $this->renderLink( $cur -1 , $this->prevText , 'pager-prev' , $cur == 1 );
124 | }
125 |
126 |
127 | if ( $this->showPageNumbers ) {
128 | if ( $cur > 5 ) {
129 | $output .= $this->renderLink( 1 , 1 , 'pager-number' );
130 | $output .= '- ...
';
131 | }
132 |
133 | for ($i = $pagenum_start ; $i <= $pagenum_end ; $i++) {
134 | if ( $i == $this->currentPage ) {
135 | $output .= $this->renderLink( $i , $i , 'pager-number active pager-number-current', false, true);
136 | } else {
137 | $output .= $this->renderLink( $i , $i , 'pager-number' );
138 | }
139 | }
140 |
141 | if ( $cur + 5 < $total_pages ) {
142 | $output .= '- ...
';
143 | $output .= $this->renderLink( $total_pages , $total_pages , 'pager-number' );
144 | }
145 | }
146 |
147 | if ($this->showNavigator) {
148 |
149 | if ( $cur < $total_pages )
150 | $output .= $this->renderLink( $cur + 1,
151 | $this->nextText , 'pager-next' , $cur == $this->totalPages );
152 |
153 | if ( $total_pages > 1 && $cur < $total_pages )
154 | $output .= $this->renderLink( $this->totalPages,
155 | $this->lastText , 'pager-last' , $cur == $this->totalPages );
156 | }
157 |
158 | $output .= '
';
159 | $output .= '
';
160 | return $output;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/WebUI/Components/Pager/Bootstrap3Pager.php:
--------------------------------------------------------------------------------
1 | firstText = _('Page.First');
39 | $this->lastText = _('Page.Last');
40 | $this->nextText = _('Page.Next');
41 | $this->prevText = _('Page.Previous');
42 |
43 | $this->currentPage = $currentPage;
44 | $this->totalPages = $totalPages;
45 | }
46 |
47 | public function setFirstPageText($text)
48 | {
49 | $this->firstText = $text;
50 | }
51 |
52 | public function setLastPageText($text)
53 | {
54 | $this->lastText = $text;
55 | }
56 |
57 | public function setNextPagetext($text)
58 | {
59 | $this->nextText = $text;
60 | }
61 |
62 | public function setPrevPageText($text)
63 | {
64 | $this->prevText = $text;
65 | }
66 |
67 | public function addClass($class)
68 | {
69 | $this->wrapperClass[] = $class;
70 | }
71 |
72 | public function mergeQuery( $orig_params , $params = array() )
73 | {
74 | $params = array_merge( $orig_params , $params );
75 | return '?' . http_build_query( $params );
76 | }
77 |
78 | public function renderLink( $num , $text = null , $moreclass = "" , $disabled = false , $active = false )
79 | {
80 | if ($text === null) {
81 | $text = $num;
82 | }
83 |
84 | if ($disabled) {
85 | return $this->renderLinkDisabled( $text , $moreclass );
86 | }
87 |
88 | $args = array_merge( $_GET , $_POST );
89 | $href = $this->mergeQuery( $args , array( "page" => $num ) );
90 | $liClass = '';
91 | if ($active) {
92 | $liClass = 'active';
93 | }
94 | return <<
96 | EOF;
97 |
98 |
99 | }
100 |
101 | public function renderLinkDisabled( $text , $moreclass = "" )
102 | {
103 | return <<
105 | EOF;
106 | }
107 |
108 | public function __toString()
109 | {
110 | return $this->render();
111 | }
112 |
113 | public function render()
114 | {
115 | $cur = $this->currentPage;
116 | $total_pages = $this->totalPages;
117 |
118 | if ($this->whenOverflow && $this->totalPages <= 1) {
119 | return "";
120 | }
121 |
122 | $pagenum_start = $cur > $this->rangeLimit ? $cur - $this->rangeLimit : 1 ;
123 | $pagenum_end = $cur + $this->rangeLimit < $total_pages ? $cur + $this->rangeLimit : $total_pages;
124 |
125 | $output = "";
126 | $output .= '';
127 |
128 | if ($this->navigator) {
129 | $output .= $this->renderLink( 1 , $this->firstText , 'pager-first' , $cur == 1 );
130 | $output .= $this->renderLink( $cur - 1 , $this->prevText , 'pager-prev' , $cur == 1 );
131 | }
132 |
133 |
134 | if ( $this->showPageNumbers ) {
135 | if ( $cur > 5 ) {
136 | $output .= $this->renderLink( 1 , 1 , 'pager-number' );
137 | $output .= '- ...
';
138 | }
139 |
140 | for ($i = $pagenum_start ; $i <= $pagenum_end ; $i++) {
141 | if ( $i == $this->currentPage ) {
142 | $output .= $this->renderLink( $i , $i , 'pager-number active pager-number-current', false, true);
143 | } else {
144 | $output .= $this->renderLink( $i , $i , 'pager-number' );
145 | }
146 | }
147 |
148 | if ( $cur + 5 < $total_pages ) {
149 | $output .= '- ...
';
150 | $output .= $this->renderLink( $total_pages , $total_pages , 'pager-number' );
151 | }
152 | }
153 |
154 | if ($this->navigator) {
155 | $output .= $this->renderLink( $cur + 1,
156 | $this->nextText , 'pager-next' , $cur == $this->totalPages );
157 | $output .= $this->renderLink( $this->totalPages,
158 | $this->lastText , 'pager-last' , $cur == $this->totalPages );
159 | }
160 | $output .= '
';
161 | return $output;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/WebUI/Components/Pager/Pager.php:
--------------------------------------------------------------------------------
1 | firstPageLabel = '«';
47 | $this->lastPageLabel = '»';
48 | $this->nextPageLabel = '›';
49 | $this->prevPageLabel = '‹';
50 | $this->currentPage = $currentPage;
51 | $this->totalPages = $totalPages;
52 | $this->baseUrl = $baseUrl;
53 | parent::__construct('ul');
54 | }
55 |
56 | public function setBaseUrl($url)
57 | {
58 | $this->baseUrl = $url;
59 | }
60 |
61 | public function setFirstPageLabel($text)
62 | {
63 | $this->firstPageLabel = $text;
64 | }
65 |
66 | public function setLastPageLabel($text)
67 | {
68 | $this->lastPageLabel = $text;
69 | }
70 |
71 | public function setNextPagetext($text)
72 | {
73 | $this->nextPageLabel = $text;
74 | }
75 |
76 | public function setPrevPageLabel($text)
77 | {
78 | $this->prevPageLabel = $text;
79 | }
80 |
81 | /**
82 | * build page query
83 | */
84 | protected function buildQuery(array $origParams , $params = array() )
85 | {
86 | $params = array_merge($origParams , $params);
87 | return $this->baseUrl . '?' . http_build_query($params);
88 | }
89 |
90 | public function __toString()
91 | {
92 | return $this->render();
93 | }
94 |
95 | protected function appendPageOmitNavItem($text = '...')
96 | {
97 | $li = new Element('li');
98 | $a = new Element('a');
99 | $a->appendTo($li);
100 | $a->setInnerHtml($text);
101 | return $li;
102 | }
103 |
104 | public function appendNavItem($text, $page, $active = false, $disabled = false) {
105 | $li = new Element('li');
106 | $a = new Element('a');
107 | $a->appendTo($li);
108 | $a->setInnerHtml($text);
109 |
110 | $href = $this->buildQuery($_REQUEST, array("page" => $page));
111 | $a->setAttributeValue('href', $href);
112 |
113 | $li->setAttributeValue('role', 'presentation');
114 | if ($disabled) {
115 | $li->addClass('disabled');
116 | $a->setAttributeValue('aria-disabled','true');
117 | }
118 |
119 | if ($active) {
120 | $li->addClass('active');
121 | $li->addClass('current');
122 | }
123 | $this->addChild($li);
124 | return $li;
125 | }
126 |
127 | public function render($attributes = array())
128 | {
129 | $cur = $this->currentPage;
130 | $totalPages = $this->totalPages;
131 |
132 | if ($this->whenOverflow && $this->totalPages <= 1) {
133 | return "";
134 | }
135 |
136 | $pageStart = $cur > $this->rangeLimit ? $cur - $this->rangeLimit : 1 ;
137 | $pageEnd = $cur + $this->rangeLimit < $totalPages ? $cur + $this->rangeLimit : $totalPages;
138 |
139 | // Create inner elements and append to children element list.
140 | if ($this->showNavigator) {
141 | if ($cur > 2) {
142 | $li = $this->appendNavItem($this->firstPageLabel, 1, $cur == 1, $cur == 1);
143 | $li->getChildAt(0)->setAttributeValue('rel','first');
144 | }
145 | $li = $this->appendNavItem($this->prevPageLabel, $cur - 1, false, $cur == 1);
146 | $li->getChildAt(0)->setAttributeValue('rel','prev');
147 | }
148 |
149 | if ($this->showNearbyPages) {
150 | if ($cur > 5) {
151 | $this->appendNavItem(1, 1);
152 | $this->appendPageOmitNavItem('...');
153 | }
154 |
155 | for ($i = $pageStart ; $i <= $pageEnd ; $i++) {
156 | $this->appendNavItem($i, $i, $cur == $i);
157 | }
158 |
159 | if ($cur + 5 < $totalPages) {
160 | $this->appendPageOmitNavItem('...');
161 | $this->appendNavItem($totalPages, $totalPages);
162 | }
163 | }
164 |
165 | if ($this->showNavigator) {
166 | $li = $this->appendNavItem($this->nextPageLabel, $cur + 1, $cur >= $totalPages);
167 | $li->getChildAt(0)->setAttributeValue('rel','next');
168 |
169 | if ($totalPages > 1 && $cur < $totalPages) {
170 | $li = $this->appendNavItem($this->lastPageLabel, $this->totalPages);
171 | $li->getChildAt(0)->setAttributeValue('rel','last');
172 | }
173 | }
174 |
175 | $html = parent::render();
176 |
177 | if ($this->navWrapper) {
178 | $nav = new Element('nav');
179 | $nav->setAttributeValue('role', 'navigation');
180 | $nav->setAttributeValue('aria-label', "Pagination");
181 | $nav->setInnerHtml($html);
182 | return $nav->render($attributes);
183 | }
184 | return $html;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/WebUI/Components/React/ReactComponent.php:
--------------------------------------------------------------------------------
1 | jsClassName = $jsClassName;
20 | $this->props = $props;
21 | $this->id = uniqid($jsClassName);
22 | $this->addClass('react-component react-app');
23 | }
24 |
25 | public function setDebug($debugLevel = 1)
26 | {
27 | $this->debug = $debugLevel;
28 | }
29 |
30 | public function render($attributes = array())
31 | {
32 | $varId = uniqid('app');
33 | // render the div element
34 | $out = parent::render($attributes) . "\n";
35 | $out .= "\n";
44 | return $out;
45 | }
46 |
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/WebUI/Core/Div.php:
--------------------------------------------------------------------------------
1 | tagName, $attributes);
12 | }
13 | }
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/WebUI/Core/Element.php:
--------------------------------------------------------------------------------
1 | tagName = $tagName;
45 | $this->setAttributeType( 'class', self::ATTR_ARRAY );
46 | $this->setAttributes( $attributes );
47 | $this->init($attributes);
48 | }
49 |
50 | public function setExtraAttributeString($attrString)
51 | {
52 | $this->extraAttributeString = $attrString;
53 | }
54 |
55 | public function isIgnoredAttribute($name)
56 | {
57 | return isset($this->_ignoredAttributes[$name]);
58 | }
59 |
60 | public function addIgnoredAttribute($name)
61 | {
62 | $this->_ignoredAttributes[$name] = true;
63 | }
64 |
65 | public function removeIgnoredAttribute($name)
66 | {
67 | unset($this->_ignoredAttributes[$name]);
68 | }
69 |
70 | /**
71 | * Register new attribute with type,
72 | * This creates accessors for objects.
73 | *
74 | * @param string $name attribute name
75 | * @param integer $type attribute type
76 | */
77 | public function setAttributeType( $name , $type )
78 | {
79 | $this->_supportedAttributes[ $name ] = $type;
80 | }
81 |
82 |
83 | /**
84 | * Remove attribute
85 | *
86 | * @param string $name
87 | */
88 | public function removeAttributeType($name)
89 | {
90 | unset( $this->_supportedAttributes[ $name ] );
91 | }
92 |
93 |
94 | /**
95 | * Get attribute value
96 | *
97 | * @param string $name
98 | * @return mixed value
99 | */
100 | public function __get($name)
101 | {
102 | if (isset($this->_attributes[ $name ] ))
103 | return $this->_attributes[ $name ];
104 | }
105 |
106 | /**
107 | * Set attribute value
108 | *
109 | * @param string $name
110 | * @param mixed $value
111 | */
112 | public function __set($name,$value)
113 | {
114 | $this->_attributes[ $name ] = $value;
115 | }
116 |
117 |
118 |
119 | /**
120 | * Check property and set attribute value without type
121 | * checking.
122 | *
123 | * If there is a property with the same name
124 | * Then the value will be set to the property.
125 | *
126 | * Or the value will be stored in $this->_attributes array.
127 | *
128 | * This is for internal use.
129 | *
130 | * @param string $name
131 | * @param mixed $arg
132 | */
133 | public function setAttributeValue($name,$arg)
134 | {
135 | if (property_exists($this, $name)) {
136 | $this->$name = $arg;
137 | } else {
138 | $this->_attributes[ $name ] = $arg;
139 | }
140 | }
141 |
142 | public function getAttributeValue($name)
143 | {
144 | if ( property_exists($this, $name) ) {
145 | return $this->$name;
146 | } elseif (array_key_exists($name, $this->_attributes)) {
147 | return $this->_attributes[ $name ];
148 | }
149 | }
150 |
151 | public function hasAttribute($name)
152 | {
153 | return property_exists($this, $name) || array_key_exists($name, $this->_attributes);
154 | }
155 |
156 | /**
157 | * Check if the attribute is registered
158 | * if it's registered, the type registered will
159 | * change the behavior of setting values.
160 | *
161 | *
162 | * @param string $name
163 | * @param array $args
164 | */
165 | public function setAttribute($name,$args)
166 | {
167 | if ($this->isIgnoredAttribute($name)) {
168 | return;
169 | }
170 |
171 | // check if it's registered.
172 | if( isset($this->_supportedAttributes[ $name ]) )
173 | {
174 | $c = count($args);
175 | $t = $this->_supportedAttributes[ $name ];
176 |
177 | if( $t != self::ATTR_FLAG && $c == 0 ) {
178 | throw new Exception( 'Attribute value is required.' );
179 | }
180 |
181 | switch( $t )
182 | {
183 | case self::ATTR_ANY:
184 | $this->setAttributeValue( $name, $args[0] );
185 | break;
186 |
187 | case self::ATTR_ARRAY:
188 | if( $c > 1 ) {
189 | $this->setAttributeValue( $name, $args );
190 | }
191 | elseif( is_array($args[0]) )
192 | {
193 | $this->setAttributeValue( $name , $args[0] );
194 | }
195 | else
196 | {
197 | $this->setAttributeValue( $name , (array) $args[0] );
198 | }
199 | break;
200 |
201 | case self::ATTR_STRING:
202 | if( is_string($args[0]) ) {
203 | $this->setAttributeValue($name,$args[0]);
204 | }
205 | else {
206 | throw new Exception("attribute value of $name is not a string.");
207 | }
208 | break;
209 |
210 | case self::ATTR_INTEGER:
211 | if( is_integer($args[0])) {
212 | $this->setAttributeValue( $name , $args[0] );
213 | }
214 | else {
215 | throw new Exception("attribute value of $name is not a integer.");
216 | }
217 | break;
218 |
219 | case self::ATTR_CALLABLE:
220 |
221 | /**
222 | * handle for __invoke, array($obj,$name), 'function_name
223 | */
224 | if( is_callable($args[0]) ) {
225 | $this->setAttributeValue( $name, $args[0] );
226 | } else {
227 | throw new Exception("attribute value of $name is not callable type.");
228 | }
229 | break;
230 |
231 | case self::ATTR_FLAG:
232 | $this->setAttributeValue($name,true);
233 | break;
234 |
235 | default:
236 | throw new Exception("Unsupported attribute type: $name");
237 | }
238 | return $this;
239 | }
240 | // save unknown attribute by default
241 | else if( $this->allowUndefinedAttribute )
242 | {
243 | $this->setAttributeValue( $name, $args[0] );
244 | }
245 | else {
246 | throw new Exception("Undefined attribute $name, Do you want to use allowUndefinedAttribute option?");
247 | }
248 | }
249 |
250 |
251 | public function __call($method,$args)
252 | {
253 | $this->setAttribute($method,$args);
254 | return $this;
255 | }
256 |
257 |
258 |
259 | /**
260 | * ==========================================
261 | * Magic methods for convinence.
262 | * ==========================================
263 | */
264 |
265 |
266 | public function offsetSet($name,$value)
267 | {
268 | $this->setAttribute($name, array($value));
269 | }
270 |
271 | public function offsetExists($name)
272 | {
273 | return isset($this->_attributes[ $name ]);
274 | }
275 |
276 | public function offsetGet($name)
277 | {
278 | if( ! isset( $this->_attributes[ $name ] ) ) {
279 | // detect type for setting up default value.
280 | $type = @$this->_supportedAttributes[ $name ];
281 | if( $type == self::ATTR_ARRAY ) {
282 | $this->_attributes[ $name ] = array();
283 | }
284 | }
285 | $val =& $this->_attributes[ $name ];
286 | return $val;
287 | }
288 |
289 | public function offsetUnset($name)
290 | {
291 | unset($this->_attributes[$name]);
292 | }
293 |
294 |
295 |
296 | // -------------------------- end of cascading attributes
297 |
298 |
299 | // =================================
300 | // Element methods and members
301 | // =================================
302 |
303 |
304 | /**
305 | * element tag name
306 | */
307 | public $tagName;
308 |
309 | /**
310 | * @var array class name
311 | */
312 | protected $class = array();
313 |
314 |
315 | /**
316 | * @var Use close tag with empty children, when this option is on,
317 | * A tag with no children is rendered as " ".
318 | */
319 | public $closeEmpty = false;
320 |
321 | /**
322 | * Children elements
323 | *
324 | * @var array
325 | */
326 | public $children = array();
327 |
328 |
329 | /**
330 | * @var id field
331 | */
332 | public $id;
333 |
334 | /**
335 | * @var array Standard attribute from element class member.
336 | */
337 | protected $standardAttributes = array(
338 | /* core attributes */
339 | 'class','id'
340 | );
341 |
342 | /**
343 | * @var array Custom attributes (append your attribute name to render class
344 | * member as attribute)
345 | */
346 | protected $customAttributes = array();
347 |
348 |
349 |
350 |
351 |
352 | protected function init($attributes)
353 | {
354 |
355 | }
356 |
357 |
358 | /**
359 | * Add custom attribute name (will be rendered)
360 | *
361 | * @param string $attribute Attribute name
362 | */
363 | public function registerCustomAttribute($attribute)
364 | {
365 | $this->customAttributes[] = $attribute;
366 | return $this;
367 | }
368 |
369 |
370 | /**
371 | * Add attribute to customAttribute list
372 | *
373 | * @param string|array $attributes
374 | *
375 | * $this->addAttributes('id class for');
376 | */
377 | public function registerCustomAttributes($attributes)
378 | {
379 | if( is_string($attributes) ) {
380 | $attributes = explode(' ',$attributes);
381 | }
382 | $this->customAttributes = array_merge( $this->customAttributes , (array) $attributes );
383 | return $this;
384 | }
385 |
386 |
387 | /**
388 | *
389 | * @param string $class class name
390 | */
391 | public function addClass($class)
392 | {
393 | if ( is_array($this->class) ) {
394 | if( is_array($class) ) {
395 | $this->class = array_merge( $this->class , $class );
396 | } else {
397 | $this->class[] = $class;
398 | }
399 | } elseif ( is_string($this->class) ) {
400 | $this->class .= " " . $class;
401 | } else {
402 | throw new Exception("Wrong class name type, array expected.");
403 | }
404 | return $this;
405 | }
406 |
407 | /**
408 | * @param string $class
409 | * @return bool
410 | */
411 | public function hasClass($class)
412 | {
413 | return array_search($class,$this->class) !== false;
414 | }
415 |
416 |
417 | /**
418 | * @param string $class class name
419 | */
420 | public function removeClass($class)
421 | {
422 | $index = array_search( $class, $this->class );
423 | array_splice( $this->class, $index , 1 );
424 | return $this;
425 | }
426 |
427 | /**
428 | * Add class
429 | */
430 | public function getClass()
431 | {
432 | return $this->class;
433 | }
434 |
435 |
436 | /**
437 | * Set element id
438 | *
439 | * @param string $id add identifier attribute
440 | */
441 | public function setId($id)
442 | {
443 | $this->id = $id;
444 | return $this;
445 | }
446 |
447 | public function getId($id)
448 | {
449 | return $this->id;
450 | }
451 |
452 |
453 | /**
454 | * Prepend Child element.
455 | *
456 | * @param FormKit\Element
457 | */
458 | public function prepend($child)
459 | {
460 | array_splice($this->children,0,0,array($child));
461 | return $this;
462 | }
463 |
464 |
465 | public function insert($child)
466 | {
467 | array_splice($this->children,0,0,array($child));
468 | return $this;
469 | }
470 |
471 | /**
472 | * Insert child at index position.
473 | *
474 | * @param FormKit\Element $child
475 | * @param integer $pos index position
476 | */
477 | public function insertChild($child, $pos = 0)
478 | {
479 | array_splice($this->children, $pos, 0, $child);
480 | return $this;
481 | }
482 |
483 |
484 | /**
485 | * Append child element at the end of list.
486 | *
487 | * @param FormKit\Element $child
488 | */
489 | public function append($child)
490 | {
491 | $this->children[] = $child;
492 | return $this;
493 | }
494 |
495 |
496 | public function setInnerText($text)
497 | {
498 | $this->children = array(new DOMText($text));
499 | }
500 |
501 |
502 | public function setInnerHtml($html)
503 | {
504 | $this->children = array(strval($html));
505 | }
506 |
507 | /**
508 | * Append DOMText node to children list.
509 | */
510 | public function appendText($text)
511 | {
512 | if ( $text ) {
513 | $this->addChild( new DOMText($text) );
514 | }
515 | return $this;
516 | }
517 |
518 |
519 |
520 | /**
521 | * As same as `append` method
522 | *
523 | * @param FormKit\Element $child
524 | */
525 | public function addChild($child)
526 | {
527 | $this->children[] = $child;
528 | return $this;
529 | }
530 |
531 |
532 | public function appendTo($container)
533 | {
534 | $container->append($this);
535 | return $this;
536 | }
537 |
538 |
539 | /**
540 | * Check if this node contains children.
541 | *
542 | * @return bool
543 | */
544 | public function hasChildren()
545 | {
546 | return !empty($this->children);
547 | }
548 |
549 | /**
550 | * Return children elements
551 | *
552 | * @return array FormKit\Element[]
553 | */
554 | public function getChildren()
555 | {
556 | return $this->children;
557 | }
558 |
559 | public function getChildrenSize()
560 | {
561 | return count($this->children);
562 | }
563 |
564 | public function getChildAt($index)
565 | {
566 | if (isset($this->children[$index])) {
567 | return $this->children[$index];
568 | }
569 | }
570 |
571 | public function removeChildAt($index)
572 | {
573 | $removed = array_splice($this->children, $index, 1);
574 | return $removed;
575 | }
576 |
577 |
578 | protected function _renderNodes(array $nodes)
579 | {
580 | $html = '';
581 | foreach($nodes as $node)
582 | {
583 | if ($node instanceof DOMText || $node instanceof DOMNode ) {
584 | // to use C14N(), the DOMNode must be belongs to an instance of DOMDocument.
585 | $dom = new DOMDocument;
586 | $node2 = $dom->importNode($node);
587 | $dom->appendChild($node2);
588 | $html .= $node2->C14N();;
589 | } elseif (is_string($node) ) {
590 | $html .= $node;
591 | } elseif (is_object($node)
592 | && ($node instanceof \FormKit\Element
593 | || $node instanceof \FormKit\Layout\BaseLayout
594 | || method_exists($node,'render') ))
595 | {
596 | $html .= $node->render();
597 | }
598 | else
599 | {
600 | throw new Exception('Unknown node type');
601 | }
602 | }
603 | return $html;
604 | }
605 |
606 |
607 | /**
608 | * Render children nodes
609 | */
610 | public function renderChildren()
611 | {
612 | if ($this->hasChildren()) {
613 | return $this->_renderNodes($this->children);
614 | }
615 | return '';
616 | }
617 |
618 | /**
619 | * Set attributes from array
620 | *
621 | * @param array $attributes
622 | */
623 | public function setAttributes($attributes)
624 | {
625 | foreach( $attributes as $k => $val ) {
626 | if ($this->isIgnoredAttribute($k)) {
627 | continue;
628 | }
629 |
630 | // this is for adding new class name with
631 | // +=newClass
632 | if (is_string($val) && strpos($val ,'+=') !== false ) {
633 | $origValue = $this->getAttributeValue($k);
634 | if( is_string($origValue) ) {
635 | $origValue .= ' ' . substr($val,2);
636 | } elseif ( is_array($origValue) ) {
637 | $origValue[] = substr($val,2);
638 | } elseif ( is_object($origValue) ) {
639 | throw new Exception('Invalid Object for attribute: ' . get_class($origValue) );
640 | } else {
641 | throw new Exception('Unknown attribute value type.');
642 | }
643 | $this->setAttributeValue($k,$origValue);
644 | } else {
645 | $this->setAttributeValue($k, $val);
646 | }
647 | }
648 | }
649 |
650 | /**
651 | * Render attributes string
652 | *
653 | * @return string Standard Attribute string
654 | */
655 | public function renderAttributes()
656 | {
657 | return $this->_renderAttributes($this->standardAttributes)
658 | . $this->_renderAttributes($this->customAttributes)
659 | . $this->_renderAttributes(array_keys($this->_attributes), true);
660 | }
661 |
662 | /**
663 | * Render attributes
664 | *
665 | * @param array $keys
666 | * @return string
667 | */
668 | protected function _renderAttributes($keys, $allowNull = false)
669 | {
670 | $html = '';
671 | foreach($keys as $key) {
672 | if ($this->hasAttribute($key)) {
673 | $val = $this->getAttributeValue($key);
674 |
675 | if (!$allowNull && ($val === NULL || (is_array($val) && empty($val))) )
676 | continue;
677 |
678 | if (is_array($val)) {
679 | // check if the array is an indexed array, check keys of
680 | // array[0..cnt]
681 | //
682 | // if it's an indexed array
683 | // for attributes key like "class", the value can be
684 | //
685 | // array('class1','class2')
686 | //
687 | // this renders the attribute as "class1 class2"
688 | //
689 | // if it's an associative array
690 | // for attribute key like "style", the value can be
691 | //
692 | // array( 'border' => '1px solid #ccc' )
693 | //
694 | // this renders the attribute as
695 | //
696 | // "border: 1px solid #ccc;"
697 | //
698 | if (array_keys($val) === range(0, count($val)-1) ) {
699 | $val = join(' ', $val);
700 | } else {
701 | $val0 = $val;
702 | $val = '';
703 | foreach( $val0 as $name => $data ) {
704 | $val .= "$name:$data;";
705 | }
706 | }
707 | }
708 | // for boolean type values like readonly attribute,
709 | // we render it as readonly="readonly".
710 | elseif ($val === TRUE || $val === FALSE) {
711 | $val = $key;
712 | }
713 |
714 | // Convert camalcase name to dash-separated name
715 | //
716 | // for dataUrl attributes, render these attributes like data-url
717 | // ( use dash separator)
718 | if ($val === NULL) {
719 | $html .= sprintf(' %s',strtolower(preg_replace('/[A-Z]/', '-$0', $key)));
720 | } else {
721 | $html .= sprintf(' %s="%s"',
722 | strtolower(preg_replace('/[A-Z]/', '-$0', $key)),
723 | htmlspecialchars( $val )
724 | );
725 | }
726 | }
727 | }
728 | return $html;
729 | }
730 |
731 |
732 | /**
733 | * Render open tag
734 | *
735 | *
736 | * $form->open();
737 | *
738 | * $form->renderChildren();
739 | *
740 | * $form->close();
741 | */
742 | public function open($attributes = array())
743 | {
744 | $this->setAttributes( $attributes );
745 | $html = '<' . $this->tagName
746 | . $this->renderAttributes()
747 | . ($this->extraAttributeString ?: '')
748 | ;
749 | // should we close it ?
750 | if ($this->closeEmpty || $this->hasChildren()) {
751 | $html .= '>';
752 | } else {
753 | $html .= '/>';
754 | }
755 | return $html;
756 | }
757 |
758 | /**
759 | * Render close tag
760 | */
761 | public function close()
762 | {
763 | $html = '';
764 | if ($this->closeEmpty || $this->hasChildren()) {
765 | $html .= '' . $this->tagName . '>';
766 | }
767 | return $html;
768 | }
769 |
770 |
771 | /**
772 | * Render the whole element.
773 | *
774 | * @param array $attributes attributes to override.
775 | * @param string HTML
776 | */
777 | public function render($attributes = array())
778 | {
779 | if (!$this->tagName) {
780 | throw new Exception('tagName is not defined.');
781 | }
782 | $html = $this->open( $attributes );
783 | $html .= $this->renderChildren();
784 | $html .= $this->close();
785 | return $html;
786 | }
787 |
788 | public function formatRender($attributes = array())
789 | {
790 | $html = $this->render($attributes);
791 | $dom = new DOMDocument('1.0');
792 | $dom->preserveWhiteSpace = true;
793 | $dom->formatOutput = true;
794 | $dom->strictErrorChecking = true;
795 | $dom->validateOnParse = false;
796 | $dom->resolveExternals = false;
797 | $dom->loadXML($html);
798 |
799 | $prettyHTML = '';
800 | foreach ($dom->childNodes as $node) {
801 | $prettyHTML .= $dom->saveXML($node) . "\n";
802 | }
803 | return $prettyHTML;
804 | }
805 |
806 |
807 | public function __toString()
808 | {
809 | return $this->render();
810 | }
811 | }
812 |
813 |
--------------------------------------------------------------------------------
/src/WebUI/Core/Span.php:
--------------------------------------------------------------------------------
1 | tagName, $attributes);
12 | }
13 | }
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tests/WebUI/Components/BreadcrumbsTest.php:
--------------------------------------------------------------------------------
1 | append('>');
11 | $el->addClass('separator');
12 |
13 | $breadcrumbs = new Breadcrumbs;
14 | ok($breadcrumbs);
15 |
16 | $breadcrumbs->setSeparatorElement($el);
17 |
18 | $breadcrumbs->appendIndexLink('Home', '/', 'The Home Page');
19 |
20 | $breadcrumbs->appendLink('Product', '/product', 'All Products');
21 |
22 | $breadcrumbs->appendLink('Product A123', '/product/a123', 'Product A123');
23 |
24 | ok($html = $breadcrumbs->render());
25 | // echo $html, "\n\n";
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/tests/WebUI/Components/Menu/MenuTest.php:
--------------------------------------------------------------------------------
1 | setTitle('Product Collection');
14 | $item->setLink('Product Collection', [
15 | 'href' => '/products',
16 | 'data-target' => '#productContainer',
17 | 'dataContainer' => 'body'
18 | ]);
19 | ok('Product Collection',$item->render());
20 | }
21 |
22 |
23 | public function testMenuFolder()
24 | {
25 | $menu = new MenuFolder('Products');
26 | $item1 = $menu->appendLink('Car', [ 'href' => '/products/car' ]);
27 | ok($item1);
28 | $item2 = $menu->appendLink('Bicycle', [ 'href' => '/products/bicycle' ]);
29 | ok($item2);
30 | $item3 = $menu->appendLink('Truck', [ 'href' => '/products/truck' ]);
31 | ok($item3);
32 |
33 | $item4 = $menu->appendLink('Others', [ 'href' => '/products/others' ]);
34 | ok($item4);
35 | $html = $menu->render();
36 | //var_dump( $html );
37 | // file_put_contents('test.html', '' . $html . '');
38 | }
39 |
40 | public function testMenu()
41 | {
42 | $menu = new Menu;
43 | $collection = $menu->appendCollection([], 'main');
44 | $collection->appendLink('Car', ['href' => '/products/car']);
45 | $collection->appendLink('Bicycle', ['href' => '/products/bicycle']);
46 | $folder = $collection->appendFolder('Others');
47 | $folder->appendLink('A', [ 'href' => '/products/a']);
48 | $folder->appendLink('B', [ 'href' => '/products/b']);
49 |
50 | $collection = $menu->appendCollection([], 'second');
51 | $folder = $collection->appendFolder('Others2');
52 | $folder->appendLink('C', [ 'href' => '/products/c']);
53 | $folder->appendLink('D', [ 'href' => '/products/d']);
54 |
55 | $html = $menu->render();
56 |
57 | //echo $html;
58 | file_put_contents('test.html', '' . $html . '');
59 | }
60 |
61 | public function testMenuItemCollection()
62 | {
63 | $menu = new MenuItemCollection;
64 | $item1 = $menu->appendLink('Car', [ 'href' => '/products/car' ]);
65 | ok($item1);
66 | $folder = $menu->appendFolder('Others');
67 | ok($folder);
68 | $folder->appendLink('A', [ 'href' => '/products/a']);
69 | $folder->appendLink('B', [ 'href' => '/products/b']);
70 | $html = $menu->render();
71 |
72 | $dom = new DOMDocument('1.0');
73 | $dom->preserveWhiteSpace = false;
74 | $dom->formatOutput = true;
75 | $dom->loadHtml($html);
76 | echo $dom->saveHTML();
77 | }
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/tests/WebUI/Components/PagerTest.php:
--------------------------------------------------------------------------------
1 | setBaseUrl('/product');
10 | $html = $pager->render();
11 |
12 | $dom = new DOMDocument('1.0');
13 | $dom->loadXml($html);
14 | $navs = $dom->getElementsByTagName('nav');
15 | is(1, $navs->length);
16 |
17 | $lis = $dom->getElementsByTagName('li');
18 | is(8, $lis->length);
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/tests/WebUI/Components/React/ReactComponentTest.php:
--------------------------------------------------------------------------------
1 | 'setting' ));
9 | $out = $component->render();
10 | echo $out;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tests/WebUI/Core/ElementTest.php:
--------------------------------------------------------------------------------
1 | 'menu'
10 | ]);
11 | $element->append(new Element('li'));
12 | $element->append(new Element('li'));
13 | $html = $element->formatRender();
14 | $this->assertNotNull($html);
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------