Skip to content

Commit e1b40ae

Browse files
committed
Repeatable widgets + major refactoring
1 parent f823306 commit e1b40ae

File tree

12 files changed

+407
-96
lines changed

12 files changed

+407
-96
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ In some situations it can be very beneficial to load widget content with AJAX.
176176

177177
Fortunately, this can be achieved very easily!
178178

179-
1. Make sure you have jquery loaded before a widget is called.
179+
1. Make sure you have jquery loaded for ajax calls before the widget is called.
180180
2. Change facade or blade directive - `Widget::` => `AsyncWidget::`, `@widget` => `@asyncWidget`
181181

182182
Done.
@@ -192,6 +192,32 @@ public function placeholder()
192192
}
193193
```
194194

195+
## Reloadable widgets
196+
197+
You can go even further and automatically reload widget every N seconds.
198+
199+
To achieve that:
200+
201+
1. Make sure you have jquery loaded for ajax calls before the widget is called.
202+
2. Set the `$reloadTimeout` property of the widget class.
203+
204+
```php
205+
class RecentNews extends AbstractWidget
206+
{
207+
/**
208+
* The number of seconds before reload.
209+
*
210+
* @var int|float
211+
*/
212+
public $reloadTimeout = 10;
213+
}
214+
```
215+
Done.
216+
217+
You should use this feature with care, because it can easily spam your app with ajax calls if timeouts are too low.
218+
Consider using web sockets too but they are waaaay harder to set up on the other hand.
219+
220+
195221
## Widget groups (extra)
196222

197223
In most cases Blade is a perfect tool fot setting the position and order of widgets.

spec/Arrilot/Widgets/Factories/AsyncWidgetFactorySpec.php

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace spec\Arrilot\Widgets\Factories;
44

5-
use Arrilot\Widgets\AbstractWidget;
65
use Arrilot\Widgets\Misc\Wrapper;
6+
use Arrilot\Widgets\WidgetId;
77
use PhpSpec\ObjectBehavior;
88

99
class AsyncWidgetFactorySpec extends ObjectBehavior
@@ -20,23 +20,26 @@ class AsyncWidgetFactorySpec extends ObjectBehavior
2020
* A mock for producing JS object for ajax.
2121
*
2222
* @param $widgetName
23-
* @param $widgetParams
23+
* @param array $widgetParams
24+
* @param int $id
2425
*
2526
* @return string
2627
*/
27-
private function mockProduceJavascriptData($widgetName, $widgetParams = [])
28+
private function mockProduceJavascriptData($widgetName, $widgetParams = [], $id = 1)
2829
{
2930
return json_encode([
31+
'id' => $id,
3032
'name' => $widgetName,
3133
'params' => serialize($widgetParams),
3234
'_token' => 'token_stub',
35+
'skip_widget_container' => 1,
3336
]);
3437
}
3538

3639
public function let(Wrapper $wrapper)
3740
{
3841
$this->beConstructedWith($this->config, $wrapper);
39-
AbstractWidget::resetId();
42+
WidgetId::reset();
4043
}
4144

4245
public function it_is_initializable()
@@ -52,7 +55,11 @@ public function it_can_run_async_widget(Wrapper $wrapper)
5255
$wrapper->csrf_token()->willReturn('token_stub');
5356

5457
$this->testDefaultSlider($config)
55-
->shouldReturn("<span id='async-widget-container-1'><script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('TestDefaultSlider', $params).", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
58+
->shouldReturn(
59+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">".
60+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('TestDefaultSlider', $params).")</script>".
61+
"</span>"
62+
);
5663
}
5764

5865
public function it_can_run_async_widget_with_placeholder(Wrapper $wrapper)
@@ -63,7 +70,11 @@ public function it_can_run_async_widget_with_placeholder(Wrapper $wrapper)
6370
$wrapper->csrf_token()->willReturn('token_stub');
6471

6572
$this->slider($config)
66-
->shouldReturn("<span id='async-widget-container-1'>Placeholder here!<script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('Slider', $params).", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
73+
->shouldReturn(
74+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">Placeholder here!".
75+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('Slider', $params).")</script>".
76+
"</span>"
77+
);
6778
}
6879

6980
public function it_can_run_multiple_async_widgets(Wrapper $wrapper)
@@ -74,10 +85,18 @@ public function it_can_run_multiple_async_widgets(Wrapper $wrapper)
7485
$wrapper->csrf_token()->willReturn('token_stub');
7586

7687
$this->slider()
77-
->shouldReturn("<span id='async-widget-container-1'>Placeholder here!<script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('Slider').", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
88+
->shouldReturn(
89+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">Placeholder here!".
90+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('Slider').")</script>".
91+
"</span>"
92+
);
7893

7994
$this->testDefaultSlider($config)
80-
->shouldReturn("<span id='async-widget-container-2'><script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('TestDefaultSlider', $params).", function(data) { $('#async-widget-container-2').replaceWith(data); })</script></span>");
95+
->shouldReturn(
96+
"<span id=\"arrilot-widget-container-2\" class=\"arrilot-widget-container\">".
97+
"<script type=\"text/javascript\">$('#arrilot-widget-container-2').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('TestDefaultSlider', $params, 2).")</script>".
98+
"</span>"
99+
);
81100
}
82101

83102
public function it_can_run_async_widget_with_additional_params(Wrapper $wrapper)
@@ -90,7 +109,11 @@ public function it_can_run_async_widget_with_additional_params(Wrapper $wrapper)
90109
$wrapper->csrf_token()->willReturn('token_stub');
91110

92111
$this->testWidgetWithParamsInRun([], 'param')
93-
->shouldReturn("<span id='async-widget-container-1'>Placeholder here!<script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('TestWidgetWithParamsInRun', $params).", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
112+
->shouldReturn(
113+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">Placeholder here!".
114+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('TestWidgetWithParamsInRun', $params).")</script>".
115+
"</span>"
116+
);
94117
}
95118

96119
public function it_can_run_async_widget_with_run_method(Wrapper $wrapper)
@@ -101,7 +124,11 @@ public function it_can_run_async_widget_with_run_method(Wrapper $wrapper)
101124
$wrapper->csrf_token()->willReturn('token_stub');
102125

103126
$this->run('testDefaultSlider', $config)
104-
->shouldReturn("<span id='async-widget-container-1'><script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('TestDefaultSlider', $params).", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
127+
->shouldReturn(
128+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">".
129+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('TestDefaultSlider', $params).")</script>".
130+
"</span>"
131+
);
105132
}
106133

107134
public function it_can_run_nested_async_widget(Wrapper $wrapper)
@@ -112,7 +139,11 @@ public function it_can_run_nested_async_widget(Wrapper $wrapper)
112139
$wrapper->csrf_token()->willReturn('token_stub');
113140

114141
$this->run('Profile\TestNamespace\TestFeed', $config)
115-
->shouldReturn("<span id='async-widget-container-1'><script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('Profile\TestNamespace\TestFeed', $params).", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
142+
->shouldReturn(
143+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">".
144+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('Profile\TestNamespace\TestFeed', $params).")</script>".
145+
"</span>"
146+
);
116147
}
117148

118149
public function it_can_run_nested_async_widget_with_dot_notation(Wrapper $wrapper)
@@ -123,6 +154,10 @@ public function it_can_run_nested_async_widget_with_dot_notation(Wrapper $wrappe
123154
$wrapper->csrf_token()->willReturn('token_stub');
124155

125156
$this->run('profile.testNamespace.testFeed', $config)
126-
->shouldReturn("<span id='async-widget-container-1'><script>$.post('/arrilot/async-widget', ".$this->mockProduceJavascriptData('Profile\testNamespace\testFeed', $params).", function(data) { $('#async-widget-container-1').replaceWith(data); })</script></span>");
157+
->shouldReturn(
158+
"<span id=\"arrilot-widget-container-1\" class=\"arrilot-widget-container\">".
159+
"<script type=\"text/javascript\">$('#arrilot-widget-container-1').load('/arrilot/load-widget', ".$this->mockProduceJavascriptData('Profile\testNamespace\testFeed', $params).")</script>".
160+
"</span>"
161+
);
127162
}
128163
}

spec/Arrilot/Widgets/Factories/WidgetFactorySpec.php

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,49 @@
55
use App\Widgets\Profile\TestNamespace\TestFeed;
66
use App\Widgets\TestDefaultSlider;
77
use App\Widgets\TestMyClass;
8+
use App\Widgets\TestRepeatableFeed;
89
use App\Widgets\TestWidgetWithDIInRun;
910
use App\Widgets\TestWidgetWithParamsInRun;
1011
use Arrilot\Widgets\Misc\Wrapper;
12+
use Arrilot\Widgets\WidgetId;
1113
use PhpSpec\ObjectBehavior;
1214
use Prophecy\Argument;
1315
use spec\Arrilot\Widgets\Dummies\Slider;
1416

1517
class WidgetFactorySpec extends ObjectBehavior
1618
{
19+
/**
20+
* A mock for producing JS object for ajax.
21+
*
22+
* @param $widgetName
23+
* @param $widgetParams
24+
*
25+
* @return string
26+
*/
27+
private function mockProduceJavascriptData($widgetName, $widgetParams = [])
28+
{
29+
return json_encode([
30+
'id' => 1,
31+
'name' => $widgetName,
32+
'params' => serialize($widgetParams),
33+
'_token' => 'token_stub',
34+
'skip_widget_container' => 1,
35+
]);
36+
}
37+
1738
protected $config = [
1839
'defaultNamespace' => 'App\Widgets',
1940
'customNamespaces' => [
20-
'slider' => 'spec\Arrilot\Widgets\Dummies',
21-
'testWidgetName' => '',
41+
'slider' => 'spec\Arrilot\Widgets\Dummies',
42+
'testRepeatableFeed' => 'spec\Arrilot\Widgets\Dummies',
43+
'testWidgetName' => '',
2244
],
2345
];
2446

2547
public function let(Wrapper $wrapper)
2648
{
2749
$this->beConstructedWith($this->config, $wrapper);
50+
WidgetId::reset();
2851
}
2952

3053
public function it_is_initializable()
@@ -37,23 +60,32 @@ public function it_can_run_widget_from_default_namespace(Wrapper $wrapper)
3760
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
3861
call_user_func_array([new TestDefaultSlider([]), 'run'], [])
3962
);
40-
$this->testDefaultSlider()->shouldReturn("Default test slider was executed with \$slides = 6");
63+
$this->testDefaultSlider()
64+
->shouldReturn(
65+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Default test slider was executed with $slides = 6</span>'
66+
);
4167
}
4268

4369
public function it_can_run_widget_from_custom_namespace(Wrapper $wrapper)
4470
{
4571
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
4672
call_user_func_array([new Slider([]), 'run'], [])
4773
);
48-
$this->slider()->shouldReturn("Slider was executed with \$slides = 6");
74+
$this->slider()
75+
->shouldReturn(
76+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Slider was executed with $slides = 6</span>'
77+
);
4978
}
5079

5180
public function it_provides_config_override(Wrapper $wrapper)
5281
{
5382
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
5483
call_user_func_array([new Slider(['slides' => 5]), 'run'], ['slides' => 5])
5584
);
56-
$this->slider(['slides' => 5])->shouldReturn("Slider was executed with \$slides = 5");
85+
$this->slider(['slides' => 5])
86+
->shouldReturn(
87+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Slider was executed with $slides = 5</span>'
88+
);
5789
}
5890

5991
public function it_throws_exception_for_bad_widget_class()
@@ -66,46 +98,101 @@ public function it_can_run_widgets_with_additional_params(Wrapper $wrapper)
6698
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
6799
call_user_func_array([new TestWidgetWithParamsInRun([]), 'run'], ['asc'])
68100
);
69-
$this->testWidgetWithParamsInRun([], 'asc')->shouldReturn("TestWidgetWithParamsInRun was executed with \$flag = asc");
101+
$this->testWidgetWithParamsInRun([], 'asc')
102+
->shouldReturn(
103+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">TestWidgetWithParamsInRun was executed with $flag = asc</span>'
104+
);
70105
}
71106

72107
public function it_can_run_widgets_with_method_injection(Wrapper $wrapper)
73108
{
74109
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
75110
call_user_func_array([new TestWidgetWithDIInRun([]), 'run'], [new TestMyClass()])
76111
);
77-
$this->testWidgetWithParamsInRun()->shouldReturn('bar');
112+
$this->testWidgetWithParamsInRun()
113+
->shouldReturn(
114+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">bar</span>'
115+
);
78116
}
79117

80118
public function it_can_run_widgets_with_run_method(Wrapper $wrapper)
81119
{
82120
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
83121
call_user_func_array([new TestDefaultSlider([]), 'run'], [])
84122
);
85-
$this->run('testDefaultSlider')->shouldReturn("Default test slider was executed with \$slides = 6");
123+
$this->run('testDefaultSlider')
124+
->shouldReturn(
125+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Default test slider was executed with $slides = 6</span>'
126+
);
86127
}
87128

88129
public function it_can_run_widgets_with_run_method_and_config_override(Wrapper $wrapper)
89130
{
90131
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
91132
call_user_func_array([new Slider(['slides' => 5]), 'run'], ['slides' => 5])
92133
);
93-
$this->run('slider', ['slides' => 5])->shouldReturn("Slider was executed with \$slides = 5");
134+
$this->run('slider', ['slides' => 5])
135+
->shouldReturn(
136+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Slider was executed with $slides = 5</span>'
137+
);
94138
}
95139

96140
public function it_can_run_nested_widgets(Wrapper $wrapper)
97141
{
98142
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
99143
call_user_func_array([new TestFeed([]), 'run'], [])
100144
);
101-
$this->run('Profile\TestNamespace\TestFeed', ['slides' => 5])->shouldReturn("Feed was executed with \$slides = 6");
145+
$this->run('Profile\TestNamespace\TestFeed', ['slides' => 5])
146+
->shouldReturn(
147+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Feed was executed with $slides = 6</span>'
148+
);
102149
}
103150

104151
public function it_can_run_nested_widgets_with_dot_notation(Wrapper $wrapper)
105152
{
106153
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
107154
call_user_func_array([new TestFeed([]), 'run'], [])
108155
);
109-
$this->run('profile.testNamespace.testFeed', ['slides' => 5])->shouldReturn("Feed was executed with \$slides = 6");
156+
$this->run('profile.testNamespace.testFeed', ['slides' => 5])
157+
->shouldReturn(
158+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Feed was executed with $slides = 6</span>'
159+
);
160+
}
161+
162+
public function it_can_run_multiple_widgets(Wrapper $wrapper)
163+
{
164+
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
165+
call_user_func_array([new Slider([]), 'run'], [])
166+
);
167+
$this->slider()
168+
->shouldReturn(
169+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Slider was executed with $slides = 6</span>'
170+
);
171+
172+
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
173+
call_user_func_array([new Slider(['slides' => 5]), 'run'], ['slides' => 5])
174+
);
175+
$this->slider(['slides' => 5])
176+
->shouldReturn(
177+
'<span id="arrilot-widget-container-2" class="arrilot-widget-container">Slider was executed with $slides = 5</span>'
178+
);
179+
}
180+
181+
public function it_can_run_async_widget(Wrapper $wrapper)
182+
{
183+
$config = [];
184+
$params = [$config];
185+
186+
$wrapper->csrf_token()->willReturn('token_stub');
187+
$wrapper->appCall(Argument::any(), Argument::any())->willReturn(
188+
call_user_func_array([new TestRepeatableFeed([]), 'run'], [])
189+
);
190+
191+
$this->testRepeatableFeed($config)
192+
->shouldReturn(
193+
'<span id="arrilot-widget-container-1" class="arrilot-widget-container">Feed was executed with $slides = 6'.
194+
'<script type="text/javascript">setTimeout( function() { $(\'#arrilot-widget-container-1\').load(\'/arrilot/load-widget\', '.$this->mockProduceJavascriptData('TestRepeatableFeed', $params).') }, 10000)</script>'.
195+
'</span>'
196+
);
110197
}
111198
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Widgets;
4+
5+
use Arrilot\Widgets\AbstractWidget;
6+
7+
class TestRepeatableFeed extends AbstractWidget
8+
{
9+
protected $slides = 6;
10+
11+
/**
12+
* The number of seconds before reload from server.
13+
*
14+
* @var float|int
15+
*/
16+
public $reloadTimeout = 10;
17+
18+
public function run()
19+
{
20+
return "Feed was executed with \$slides = ".$this->slides;
21+
}
22+
}

0 commit comments

Comments
 (0)