Showing
11 changed files
with
148 additions
and
30 deletions
| ... | @@ -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 | } | ... | ... |
-
Please register or sign in to post a comment