Dan Brown

Started the page attributes interface

...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
6 */ 6 */
7 class Attribute extends Model 7 class Attribute extends Model
8 { 8 {
9 - protected $fillable = ['name', 'value']; 9 + protected $fillable = ['name', 'value', 'order'];
10 10
11 /** 11 /**
12 * Get the entity that this attribute belongs to 12 * Get the entity that this attribute belongs to
......
...@@ -38,18 +38,15 @@ class AttributeController extends Controller ...@@ -38,18 +38,15 @@ class AttributeController extends Controller
38 */ 38 */
39 public function updateForEntity($entityType, $entityId, Request $request) 39 public function updateForEntity($entityType, $entityId, Request $request)
40 { 40 {
41 -
42 - $this->validate($request, [
43 - 'attributes.*.name' => 'required|min:3|max:250',
44 - 'attributes.*.value' => 'max:250'
45 - ]);
46 -
47 $entity = $this->attributeRepo->getEntity($entityType, $entityId, 'update'); 41 $entity = $this->attributeRepo->getEntity($entityType, $entityId, 'update');
48 if ($entity === null) return $this->jsonError("Entity not found", 404); 42 if ($entity === null) return $this->jsonError("Entity not found", 404);
49 43
50 $inputAttributes = $request->input('attributes'); 44 $inputAttributes = $request->input('attributes');
51 $attributes = $this->attributeRepo->saveAttributesToEntity($entity, $inputAttributes); 45 $attributes = $this->attributeRepo->saveAttributesToEntity($entity, $inputAttributes);
52 - return response()->json($attributes); 46 + return response()->json([
47 + 'attributes' => $attributes,
48 + 'message' => 'Attributes successfully updated'
49 + ]);
53 } 50 }
54 51
55 /** 52 /**
......
...@@ -72,7 +72,7 @@ class PageController extends Controller ...@@ -72,7 +72,7 @@ class PageController extends Controller
72 $this->checkOwnablePermission('page-create', $book); 72 $this->checkOwnablePermission('page-create', $book);
73 $this->setPageTitle('Edit Page Draft'); 73 $this->setPageTitle('Edit Page Draft');
74 74
75 - return view('pages/create', ['draft' => $draft, 'book' => $book]); 75 + return view('pages/edit', ['page' => $draft, 'book' => $book, 'isDraft' => true]);
76 } 76 }
77 77
78 /** 78 /**
......
...@@ -80,6 +80,7 @@ class AttributeRepo ...@@ -80,6 +80,7 @@ class AttributeRepo
80 $entity->attributes()->delete(); 80 $entity->attributes()->delete();
81 $newAttributes = []; 81 $newAttributes = [];
82 foreach ($attributes as $attribute) { 82 foreach ($attributes as $attribute) {
83 + if (trim($attribute['name']) === '') continue;
83 $newAttributes[] = $this->newInstanceFromInput($attribute); 84 $newAttributes[] = $this->newInstanceFromInput($attribute);
84 } 85 }
85 86
......
...@@ -18,10 +18,12 @@ class CreateAttributesTable extends Migration ...@@ -18,10 +18,12 @@ class CreateAttributesTable extends Migration
18 $table->string('entity_type', 100); 18 $table->string('entity_type', 100);
19 $table->string('name'); 19 $table->string('name');
20 $table->string('value'); 20 $table->string('value');
21 + $table->integer('order');
21 $table->timestamps(); 22 $table->timestamps();
22 23
23 $table->index('name'); 24 $table->index('name');
24 $table->index('value'); 25 $table->index('value');
26 + $table->index('order');
25 $table->index(['entity_id', 'entity_type']); 27 $table->index(['entity_id', 'entity_type']);
26 }); 28 });
27 } 29 }
......
...@@ -400,4 +400,96 @@ module.exports = function (ngApp, events) { ...@@ -400,4 +400,96 @@ module.exports = function (ngApp, events) {
400 400
401 }]); 401 }]);
402 402
403 + ngApp.controller('PageAttributeController', ['$scope', '$http', '$attrs',
404 + function ($scope, $http, $attrs) {
405 +
406 + const pageId = Number($attrs.pageId);
407 + $scope.attributes = [];
408 +
409 + /**
410 + * Push an empty attribute to the end of the scope attributes.
411 + */
412 + function addEmptyAttribute() {
413 + $scope.attributes.push({
414 + name: '',
415 + value: ''
416 + });
417 + }
418 +
419 + /**
420 + * Get all attributes for the current book and add into scope.
421 + */
422 + function getAttributes() {
423 + $http.get('/ajax/attributes/get/page/' + pageId).then((responseData) => {
424 + $scope.attributes = responseData.data;
425 + addEmptyAttribute();
426 + });
427 + }
428 + getAttributes();
429 +
430 + /**
431 + * Set the order property on all attributes.
432 + */
433 + function setAttributeOrder() {
434 + for (let i = 0; i < $scope.attributes.length; i++) {
435 + $scope.attributes[i].order = i;
436 + }
437 + }
438 +
439 + /**
440 + * When an attribute changes check if another empty editable
441 + * field needs to be added onto the end.
442 + * @param attribute
443 + */
444 + $scope.attributeChange = function(attribute) {
445 + let cPos = $scope.attributes.indexOf(attribute);
446 + if (cPos !== $scope.attributes.length-1) return;
447 +
448 + if (attribute.name !== '' || attribute.value !== '') {
449 + addEmptyAttribute();
450 + }
451 + };
452 +
453 + /**
454 + * When an attribute field loses focus check the attribute to see if its
455 + * empty and therefore could be removed from the list.
456 + * @param attribute
457 + */
458 + $scope.attributeBlur = function(attribute) {
459 + let isLast = $scope.attributes.length - 1 === $scope.attributes.indexOf(attribute);
460 + if (attribute.name === '' && attribute.value === '' && !isLast) {
461 + let cPos = $scope.attributes.indexOf(attribute);
462 + $scope.attributes.splice(cPos, 1);
463 + }
464 + };
465 +
466 + $scope.saveAttributes = function() {
467 + setAttributeOrder();
468 + let postData = {attributes: $scope.attributes};
469 + $http.post('/ajax/attributes/update/page/' + pageId, postData).then((responseData) => {
470 + $scope.attributes = responseData.data.attributes;
471 + addEmptyAttribute();
472 + events.emit('success', responseData.data.message);
473 + })
474 + };
475 +
476 + }]);
477 +
403 }; 478 };
479 +
480 +
481 +
482 +
483 +
484 +
485 +
486 +
487 +
488 +
489 +
490 +
491 +
492 +
493 +
494 +
495 +
......
...@@ -202,3 +202,17 @@ $btt-size: 40px; ...@@ -202,3 +202,17 @@ $btt-size: 40px;
202 color: #EEE; 202 color: #EEE;
203 } 203 }
204 } 204 }
205 +
206 +// Attribute form
207 +.floating-toolbox {
208 + background-color: #FFF;
209 + border: 1px solid #BBB;
210 + border-radius: 3px;
211 + padding: $-l;
212 + position: fixed;
213 + right: $-xl*2;
214 + top: 100px;
215 + z-index: 99;
216 + height: 800px;
217 + overflow-y: scroll;
218 +}
...\ No newline at end of file ...\ No newline at end of file
......
1 -@extends('base')
2 -
3 -@section('head')
4 - <script src="/libs/tinymce/tinymce.min.js?ver=4.3.7"></script>
5 -@stop
6 -
7 -@section('body-class', 'flexbox')
8 -
9 -@section('content')
10 -
11 - <div class="flex-fill flex">
12 - <form action="{{$book->getUrl() . '/page/' . $draft->id}}" method="POST" class="flex flex-fill">
13 - @include('pages/form', ['model' => $draft])
14 - </form>
15 - </div>
16 - @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $draft->id])
17 -@stop
...\ No newline at end of file ...\ No newline at end of file
...@@ -10,9 +10,24 @@ ...@@ -10,9 +10,24 @@
10 10
11 <div class="flex-fill flex"> 11 <div class="flex-fill flex">
12 <form action="{{$page->getUrl()}}" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill"> 12 <form action="{{$page->getUrl()}}" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
13 + @if(!isset($isDraft))
13 <input type="hidden" name="_method" value="PUT"> 14 <input type="hidden" name="_method" value="PUT">
15 + @endif
14 @include('pages/form', ['model' => $page]) 16 @include('pages/form', ['model' => $page])
15 </form> 17 </form>
18 +
19 + <div class="floating-toolbox" ng-controller="PageAttributeController" page-id="{{ $page->id or 0 }}">
20 + <form ng-submit="saveAttributes()">
21 + <table>
22 + <tr ng-repeat="attribute in attributes">
23 + <td><input type="text" ng-model="attribute.name" ng-change="attributeChange(attribute)" ng-blur="attributeBlur(attribute)" placeholder="Attribute Name"></td>
24 + <td><input type="text" ng-model="attribute.value" ng-change="attributeChange(attribute)" ng-blur="attributeBlur(attribute)" placeholder="Value"></td>
25 + </tr>
26 + </table>
27 + <button class="button pos" type="submit">Save attributes</button>
28 + </form>
29 + </div>
30 +
16 </div> 31 </div>
17 @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id]) 32 @include('partials/image-manager', ['imageType' => 'gallery', 'uploaded_to' => $page->id])
18 33
......
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
41 @include('form/text', ['name' => 'name', 'placeholder' => 'Page Title']) 41 @include('form/text', ['name' => 'name', 'placeholder' => 'Page Title'])
42 </div> 42 </div>
43 </div> 43 </div>
44 +
44 <div class="edit-area flex-fill flex"> 45 <div class="edit-area flex-fill flex">
45 @if(setting('app-editor') === 'wysiwyg') 46 @if(setting('app-editor') === 'wysiwyg')
46 <textarea id="html-editor" tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" name="html" rows="5" 47 <textarea id="html-editor" tinymce="editorOptions" mce-change="editorChange" mce-model="editContent" name="html" rows="5"
......
...@@ -97,19 +97,32 @@ class AttributeTests extends \TestCase ...@@ -97,19 +97,32 @@ class AttributeTests extends \TestCase
97 ['name' => 'country', 'value' => 'England'], 97 ['name' => 'country', 'value' => 'England'],
98 ]; 98 ];
99 99
100 + // Do update request
100 $this->asAdmin()->json("POST", "/ajax/attributes/update/page/" . $page->id, ['attributes' => $testJsonData]); 101 $this->asAdmin()->json("POST", "/ajax/attributes/update/page/" . $page->id, ['attributes' => $testJsonData]);
102 + $updateData = json_decode($this->response->getContent());
103 + // Check data is correct
104 + $testDataCorrect = true;
105 + foreach ($updateData->attributes as $data) {
106 + $testItem = ['name' => $data->name, 'value' => $data->value];
107 + if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
108 + }
109 + $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
110 + $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($updateData)));
111 + $this->assertTrue(isset($updateData->message), "No message returned in attribute update response");
112 +
113 + // Do get request
101 $this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id); 114 $this->asAdmin()->get("/ajax/attributes/get/page/" . $page->id);
102 - $jsonData = json_decode($this->response->getContent()); 115 + $getResponseData = json_decode($this->response->getContent());
103 // Check counts 116 // Check counts
104 - $this->assertTrue(count($jsonData) === count($testJsonData), "The received attribute count is incorrect"); 117 + $this->assertTrue(count($getResponseData) === count($testJsonData), "The received attribute count is incorrect");
105 // Check data is correct 118 // Check data is correct
106 $testDataCorrect = true; 119 $testDataCorrect = true;
107 - foreach ($jsonData as $data) { 120 + foreach ($getResponseData as $data) {
108 $testItem = ['name' => $data->name, 'value' => $data->value]; 121 $testItem = ['name' => $data->name, 'value' => $data->value];
109 if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false; 122 if (!in_array($testItem, $testResponseJsonData)) $testDataCorrect = false;
110 } 123 }
111 $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s"; 124 $testMessage = "Expected data was not found in the response.\nExpected Data: %s\nRecieved Data: %s";
112 - $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($jsonData))); 125 + $this->assertTrue($testDataCorrect, sprintf($testMessage, json_encode($testResponseJsonData), json_encode($getResponseData)));
113 } 126 }
114 127
115 } 128 }
......