How to organize sections of similar nature in Laravel? - laravel

The Summary
Consider a Laravel project having, among all other things, the following sections: Files, Documents, Images, Videos, Tests.
Each of these sections stores some sort of data:
Documents contain various text documents.
Images contain image files of different extensions.
Videos contain video files of different extensions.
Files contain the rest of the files, not fitting into any of the sections above.
Tests contain sets of series of questions with an ability to respond.
Despite the diverse nature of their content, these sections are organized in the same fashion: each of them has data split by categories (which, by using the nested set model, may or may not include subcategories), and each item inside of them can be viewed individually on its full page.
So, each of these sections has the following route organization:
/{section} // The homepage of a section
/{section}/{category} // The page of a section's top-level category
/{section}/{parent_category}/{child_category} // The page of a section's subcategory
/{section}/{item_category}/{item} // The page of a section's specific item
Inside routes/web.php, I have made use of route groups from Laravel to achieve this:
Route::prefix('documents')->name('documents.')->controller(DocumentsController::class)->group(function () {
Route::get('/{category_slug}/{entry_slug}', 'entry')->name('entry');
Route::get('/{category_slug}', 'category')->name('category');
Route::get('', 'all')->name('all');
});
Route::prefix('images')->name('images.')->controller(ImagesController::class)->group(function () {
Route::get('/{category_slug}/{entry_slug}', 'entry')->name('entry');
Route::get('/{category_slug}', 'category')->name('category');
Route::get('', 'all')->name('all');
});
Route::prefix('videos')->name('videos.')->controller(VideosController::class)->group(function () {
Route::get('/{category_slug}/{entry_slug}', 'entry')->name('entry');
Route::get('/{category_slug}', 'category')->name('category');
Route::get('', 'all')->name('all');
});
Route::prefix('files')->name('files.')->controller(FilesController::class)->group(function () {
Route::get('/{category_slug}/{entry_slug}', 'entry')->name('entry');
Route::get('/{category_slug}', 'category')->name('category');
Route::get('', 'all')->name('all');
});
Route::prefix('tests')->name('tests.')->controller(TestsController::class)->group(function () {
Route::get('/{category_slug}/{entry_slug}', 'entry')->name('entry');
Route::get('/{category_slug}', 'category')->name('category');
Route::get('', 'all')->name('all');
});
This way I can attach different views and different data to different sections, but, as you can see, it becomes clumsier and clumsier if I'll add more sections, which is clearly a sign that what I am trying to do is not done right.
The Question
Simple and clean, how do I get rid of this duplicated code without opening new issues with the implementation? Most importantly, how do I organize such sections for getting a centralized control over their abstraction?
They are not similar to any data structure or database design model of which I know. I suspect that polymorphic relationships should be useful somehow, but even if I choose to use a polymorphic relationship, creating some sort of datastorable, I don't know how do I make routes of these abstractions which won't involve a lot of duplicate code.
Please do not respond the typical workaround by creating an array of titles and controllers and iterating over them in order to remove the duplicate code. Not only it forces to either typecast associative arrays to objects (which is considered a bad practice) in order to access values by -> object operator to keep it in line with Laravel's approach, but also it's adding its own layer of unnecessarity by redundant declarations.

Related

Vuetify data table - export to excel

I want to export data from vuetify data table to excel. If user change sorting or filter data I want to export the data as I can see. Every operation is client-side, server only generates whole data source.
<v-data-table
:headers="headers"
:items="items"
:items-per-page="5"
></v-data-table>
...
created() {
this.items = await this.$axios.$get('/api/get-items')
}
My idea is take json data and use e.g. exceljs to generate excel file. But, there is only one prop (items), which is array before client-side operations (like sorting or filtering). Of cource, that I can take html code and parse data to json or edit api to accept sorting, filterig etc., but this is ugly way. Is there any other solution?
You can do it client-side with one major restriction: you will not be able to use pagination on this table.
If it's OK for you, a special #current-items event should to the trick:
<v-data-table
:headers="headers"
:items="desserts"
:items-per-page="-1"
#current-items="onCurrentItemsChanged"
></v-data-table>
...
data () {
return {
currentItems: [],
...
}
},
methods: {
onCurrentItemsChanged(val) {
this.currentItems = val;
}
}
This emits the items every time the internalCurrentItems is changed.
But when you apply pagination, this event will return only current page data. Not all table elements are displayed at once (to improve performance, I guess).
You won't be able to implement this via HTML code parse either, because v-data-table produce <tr>s only for the elements of the current page.
So if you are not satisfied with this restriction, it'd be better to do it server-side.
You can test this at CodePen.

CKEditor 5: How can I load to/save from model instead of view?

My application uses CKEditor 5 to allow users to edit rich text data. These texts support some application-specific custom elements (Web Components), and I want to extend CKEditor with custom plugins that support inserting such custom elements. I seem to be almost there, but I'm having some difficulties getting these custom elements into and out of the CKEditor instance properly.
Current plugin implementations
I mainly followed the Implementing an inline widget tutorial from the CKEditor 5 documentation. As an example, I would like to support a custom element like <product-info product-id="123"></product-info>, which in CKEditor should be rendered as a simple <span> with a specific class for some styling.
In my editing plugin, I first define the extension to the schema:
const schema = this.editor.model.schema;
schema.register('product-info', {
allowWhere: '$text',
isInline: true,
isObject: true,
allowAttributes: [ 'product-id' ]
});
I then define the upcast and downcast converters, closely sticking to the tutorial code:
const conversion = this.editor.conversion;
conversion.for('upcast').elementToElement({
view: {
name: 'span',
classes: [ 'product-info' ]
},
model: (viewElement, { writer: modelWriter }) => {
const id = viewElement.getChild(0).data.slice(1, -1);
return modelWriter.createElement('product-info', { 'product-id': id });
}
});
conversion.for('editingDowncast').elementToElement({
model: 'product-info',
view: (modelItem, { writer: viewWriter }) => {
const widgetElement = createProductInfoView(modelItem, viewWriter);
return toWidget(widgetElement, viewWriter);
}
});
conversion.for('dataDowncast').elementToElement({
model: 'product-info',
view: (modelItem, { writer: viewWriter }) => createProductInfoView(modelItem, viewWriter)
});
function createProductInfoView(modelItem, viewWriter) {
const id = modelItem.getAttribute('product-id');
const productInfoView = viewWriter.createContainerElement(
'span',
{ class: 'product-info' },
{ isAllowedInsideAttributeElement: true }
);
viewWriter.insert(
viewWriter.createPositionAt(productInfoView, 0),
viewWriter.createText(id)
);
return productInfoView;
}
Expected behavior
The idea behind all this is that I need to support the custom <product-info> elements stored in user data in the backend. CKEditor, which is used by users to edit that data, should load these custom elements and transform them into a styled <span> for display purposes while editing. These should be treated as inline widgets since they should only be able to be inserted, moved, copied, pasted, deleted as a whole unit. A CKEditor plugin should allow the user to create new such elements to be inserted into the text, which will then also be <span>s in the editing view, but <product-info>s in the model, which should also be written back to the backend database.
In other words, I expected this to ensure a direct mapping between element <product-info product-id="123"></product-info> in the model, and <span class="product-info">123</span> in the view, to support inserting and moving of <product-info> elements by the user.
Actual result
In short, I seem to be unable to get CKEditor to load data containing <product-info> elements, and unable to retrieve the model representation of these custom elements for backend storage. All operations to insert data to CKEditor from source, or to retrieve CKEditor data for sending to the backend, seem to operate on the view.
For example, if I preload CKEditor contents either by setting the inner content of the element that is replaced with the editor instance, or inserting it like this:
const viewFragment = editor.data.processor.toView(someHtml);
const modelFragment = editor.data.toModel(viewFragment);
editor.model.insertContent(modelFragment);
I see the following behavior (verified using CKEditor Inspector):
When inserting the custom element, i.e. <product-info product-id="123"></product-info>, the element is stripped. It's not present in either the model nor the view.
When inserting the view representation, i.e. <span class="product-info">123</span> I get the representation that I want, i.e. that same markup in CKEditor's view, and the <product-info product-id="123"></product-info> tag in the model.
This is exactly the opposite of what I want! In my backend, I don't want to store the view representation that I created for editing purposes, I want to store the actual custom element. Additionally:
My UI plugin to insert new product info elements, uses a command that does the following:
execute({ value }) {
this.editor.model.change( writer => {
const productInfo = writer.createElement('product-info', {
'product-id': value
});
this.editor.model.insertContent(productInfo);
writer.setSelection(productInfo, 'on');
});
}
which also works as I want it to, i.e. it generates the product-info tag for the model and the span for the view. But, of course, when loading an entire source text when initialising the editor with data from the backend, I can't use this createElement method.
Conversely, in order to retrieve the data from CKEditor for saving, my application uses this.editor.getData(). There, these proper pairs of <product-info> model elements and <span> view elements get read out in their view representation, instead of their model representation – not what I want for storing this data back!
The question
My question is: what do I need to change to be able to load the data into CKEditor, and get it back out of the CKEditor, using the custom element, rather than the transformed element I want to show only for editing purposes? Put differently: how can I make sure the content I insert into CKEditor is treated as the model representation, and how do I read out the model representation from my application?
I'm confused about this because if the model representation is something that is only supposed to be used internally by CKEditor, and not being able to be set or retrieved from outside – then what is the purpose of defining the schema and these transformations in the first place? It will only ever be visible to CKEditor, or someone loading up the CKEditor Inspector, but of no use to the application actually integrating the editor.
Sidenote: an alternative approach
For a different approach, I tried to forgo the transformation to <span>s entirely, and just use the custom element <product-info>, unchanged, in both the model and the view, by using the General HTML Support functionality. This worked fine, since this time no transformation was needed, all I had to do was to set the schema in order for CKEditor to accept and pass through the custom elements.
The reason I can't go with this approach is that in my application, these custom components are handled using Angular Elements, so they will actually be Angular components. The DOM manipulation seems to interfere with CKEditor, the elements are no longer treated as widgets, and there are all manner of bugs and side effects that come with it. Elements show up fine in CKEditor at first, but things start falling apart when trying to select or move them. Hence my realisation that I probably need to create a custom representation for them in the CKEditor view, so they're not handled by Angular and preventing these issues.

Updating Django form based on input

I want to append more fields based on a users selection of Type of Event.
What is the best way to add additional fields based on the users selection without refreshing the page?
I was thinking ajax call when user clicks Add the details and return html?
Is there a way to do this using the template system with a series of if/else conditionals?
Two solutions come to mind here...
1)
Attach a jQuery change handler to the 'Type of Event' select element, and execute an ajax request to return the dynamic fields that will need to be displayed.
$('#TYPE_OF_EVENT_ID').change(function() {
$.get('/api/to/return/dynamic/fields/', {'type_of_event': $(this).val()}, function(data, textStatus, jqXHR) {
# Update DOM with dynamic content return by data (should probably be JSON)
});
});
2)
Hard code the logic straight into your javascript to handle the case statements to display the dynamic fields depending upon the value which is selected in the 'Type of Event' select element.
$('#TYPE_OF_EVENT_ID').change(function() {
switch($(this).val()) {
case 'Special Event':
# Show Special Event Fields
case 'Non Special Event':
# Show Non Special Event Fields
}
});
I'd recommend option 1 as it scales better keeping this logic on the server to be database driven.

Issues with angular $watch and retrieving data from database

I'm a novice programming trying to put together a web application with Angular, node.js, and the graph database neo4j.
I would like to load content from my database dynamically based on the user selecting (or rejecting) terms (clicking buttons). Every time a button is clicked the relevant term is added to an array (either exclude or include). The idea is a new call to the database would be made each time a new term is selected.
I'm stuck right now on how to go about making calls to the database to retrieve the content. I'm trying to watch the arrays for changes using $watch. Something is going wrong and I'm having issues troubleshooting the problem.
Here is the controller code:
angular.module('myApp.controllers', []).
controller('content',function($scope,$http, queryTerms, $watch){
//watch arrays of terms for changes and fetch results based on what is selected
$watch(function() { return angular.toJson( [ queryTerms.includedTerms, queryTerms.excludedTerms ] ) },
function() {
$http({
method:'get',
url:'/query/getContent',
params: {includeTerms:queryTerms.includedTerms , excludeTerms:queryTerms.excludedTerms}
}).
success(function(data){
//feed content data to display for viewing
}).
error(function(data){
$scope.test = "Error :("
});
});
});
I'm getting the following error when I use $watch:
Error: Unknown provider: $watchProvider <- $watch
Is this a terrible stupid way to go about this in general? Any advice would be greatly appreciated- I'm learning as I'm going and so far the advice I've gotten on here has be amazing. Thanks!
Use $scope.$watch instead.
controller('content', function ($scope, $http, queryTerms) {
$scope.$watch(function () {
return angular.toJson([queryTerms.includedTerms, queryTerms.excludedTerms])
},...

implement chanied filters/seach options in a datagrid using ajax

Let´s say I have some sort of datagrid and I want to add a couple chained filters like in this site:
http://www.yelp.com/search?find_desc=bar&ns=1&find_loc=Minneapolis%2C+MN
(sort by,distance,price etc).
Each time a user clciked in a filter link it will update the content of datagrid accordingly. But I would also need to update the links in other filters to take account of the changes. Ex: if i change the order field I need to add/update ?order_field=x in all the other filters links.
What you think is the best way to implement such scenario?
Should i create a function that, when a filter link is clicked, it update the query string params of all the other filters? Or use hidden fields to record the selected option in each filter?
I would like a reusable solution if possible.
Since the data is loading via AJAX, there shouldn't be any links to update - at least not if you mean anchor tags <a>. You don't even need to store the filters in a hidden field.
I would store all the filters as a JSON object. Depending on how your API is set up, you may have to convert the JSON object to something usable by your API or you may even be able to pass on the JSON object directly in the $.ajax request.
This sample code assumes you have a textbox with id="price" in the markup. I intentionally left convert_filters_to_parameters blank because you didnt provide any details as to your API. jQuery will in turn serialize those parameters into a GET or POST request before it sends them out.
var filters = {
distance:null,
price:null,
sortBy:'distance'
}
//this assumes you have a textbox with id="price"
$('#price').changed(function()
{
filters.price = $(this).val();
refresh_data();
});
function refresh_data()
{
var parameters = convert_filters_to_parameters(filters);
$.ajax('/my_api',
{
//i left out a lot of properties here for brevity
data: parameters,
success: function(response) { alert(response); }
});
}

Resources