Laravel Nova Tool - Send Metadata to Vue - laravel-nova

I'm creating a Tool for Laravel Nova 2.0
In my Tool I want to send a list of stuff to the Vue component:
in the PHP src for my tool I have a function that generates the "meta", as suggested in the documentation here:
public function stuff() {
$stuff = [];
...
return $this->withMeta(['stuff' => $stuff]);
}
In my NovaServiceProvider.php I instantiate the tool and call the meta function. i.e.
public function tools()
{
return [
(new Tool())->stuff(),
];
}
However, nothing is passed to the Tool.vue component, (I have spent sometime inspecting it!) i.e.:
mounted() {
console.log(this.stuff); // undefined
},
Issue is discussed here: https://github.com/laravel/nova-issues/issues/761, however note that I am using a Tool and not a ResourceTool, or a Card.
Is this a bug with Tools, or something I'm doing wrong? Is there a workaround?

I haven't tried creating a custom tool yet, but for the custom field you can get the metadata using:
mounted() {
console.log(this.field.stuff);
},
if you're having troubles with similar stuff, i suggest printing the content of the class in the console in a json format, it makes it easier for you to troubleshoot the problem
mounted() {
console.log(JSON.stringify(this));
},
Although i don't think this is the problem can you try modifying your stuff() function to:
public function stuff()
{
$stuff = [];
$this->withMeta(['stuff' => $stuff]);
return $this;
}

Related

Laravel Nova card localization

I'm trying to add localization support for a card built for the Laravel Nova dashboard.
I already created a folder in /resources/lang containing the JSON language files in a format like en.json. The files get published (copied) with the publish command but the loadJsonTranslationsFrom() does not seem to do anything:
class CardServiceProvider extends ServiceProvider
{
public function boot()
{
$this->publishes(
[__DIR__ . '/../resources/lang' => resource_path('lang/vendor/my-custom-card')],
'my-custom-card-lang'
);
$this->loadJsonTranslationsFrom(resource_path('lang/vendor/my-custom-card'));
}
}
This is how the markup in Card.vue looks like:
{{__('Title')}}
How can I test if the JSON files are loaded correctly? What am I missing?
The question is how do I support localization for cards in Laravel Nova?
Card localization has been solved in Laravel Nova 2.
To localize strings use the __ helper within your Vue components and load the according translation files within your NovaServiceProvider:
Nova::serving(function (ServingNova $event) {
Nova::script('{{ component }}', __DIR__.'/../dist/js/card.js');
Nova::style('{{ component }}', __DIR__.'/../dist/css/card.css');
Nova::translations(__DIR__.'/../resources/lang/en/card.json');
});
An exemplary implementation can be found on GitHub.
Further information is now available in the documentation.
I'm having the same issue, but for a tool, also in Nova 2.0.
I found a somewhat elegant solution - maybe it helps someone nonetheless.
Create en.json in /nova-components/{your-tool}/resources/lang/
In /nova-components/{your-tool}/resources/js/tool.js add Vue.mixin(require('./translation'));.
It should look something like this:
Nova.booting((Vue, router, store) => {
router.addRoutes([
{your-routes}
]);
Vue.mixin(require('./translation')); <-------------- add this line!
});
Create the /nova-components/{your-tool}/resources/js/translation.js, it should look like this:
module.exports = {
methods: {
__(key, replace) {
var translations = _.merge(window.config.translations, window.config['tool-translations']);
var translation = translations[key]
? translations[key]
: key;
_.forEach(replace, (value, key) => {
translation = translation.replace(':' + key, value)
});
return translation;
}
}
};
Now you have to add the following to the Nova::serving() function inside the boot() function of your /nova-components/{your-tool}/src/ToolServicePrivoder.php file:
Nova::provideToScript([
'tool-translations' => $this->getTranslations(),
]);
Now add below said boot() function the following:
private static function getTranslations()
{
$translationFile = __DIR__ . '/../resources/lang/' . app()->getLocale() . '.json';
if (!is_readable($translationFile)) {
return [];
}
return json_decode(file_get_contents($translationFile), true);
}

Swashbuckle UI displays same information for different versions

We've implemented Swashbuckle on our Web Api project and i'm noticing that the SwaggerUI test harness displays the exact same information for any version specified in the address. More specifically it displays the swagger information for which ever VersionInfoBuilder comes first within the SwaggerConfig.
So for example if i navigate to "/preview/swagger/index" - the UI displayed is all v1 information not related to version 'preview'.
What am I doing wrong here?
public class SwaggerConfig
{
private SwaggerConfig() { }
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger("{apiVersion}/swagger", c =>
{
c.MultipleApiVersions(
(apiDesc, version) =>
{
var path = apiDesc.RelativePath.Split('/');
var pathVersion = path[0];
return CultureInfo.InvariantCulture.CompareInfo.IndexOf(pathVersion, version, CompareOptions.IgnoreCase) >= 0;
},
vc =>
{
vc.Version("v1", "Api - v1");
vc.Version("preview", "Api - Preview");
});
})
.EnableSwaggerUi("{apiVersion}/swagger/{*assetPath}", c =>
{
c.DisableValidator();
});
}
}
I don't think the configuration is wrong, it is just that your expectations are not correct.
You do not have to navigate to another Swagger UI (at preview/docs/index) but you have to point Swagger UI at another specification. Just enter http://yourserver:yourport/preview/swagger in the input box in the header and press Explore. Swagger UI will now load and display the preview specification.

ajax request php class function

i have content listed in a div and i have a dropdown with various options to order and filter that content.
I'm using ajax to filter/order that content and is working but i use other php page with the content i want on the div that has the content, like this
function order(str){
$.post("order_products.php",
{
q: str,
},
function(data, status){
document.getElementById("txtHint").innerHTML = data;
});
}
What i wanted was to instead of putting the code (data) to change in another page just for that, i could put that code inside a class php function that i have.
<?php
class products{
function list(){
blablabla
}
That way i would "save space" and organize everything, considering that i have many other things to order/filter but i don't know to to make the ajax request to that function, or if it's possible without having a page in between and then get the response from the function and put it on the div.
You can do this using Laravel by setting up a route to a function that will do the ordering. Please note I've made a lot of assumptions in the following answer as I can't see all your code and have made it quite general, please adjust the code to your project or provide more details of your code if you don't understand the answer fully.
routes.php
Route::post('products/order', [
'as' => 'products.order',
'uses' => 'ProductsController#orderProducts'
]);
Your view (assuming you're using blade)
$txtHint = $('#txtHint'); // grab the reference to txtHint dropdown
$.post( '{{ route("products.order") }}', // hit our route
{
q: str,
},
function(data, status){
$txtHint.empty(); // clear the dropdown
// loop through the data and assign each element to the dropdown
$.each(data, function(value, key) {
$txtHint.append($("<option></option>")
.attr("value", value)
.text(key));
});
});
ProductsController.php
public function orderProducts()
{
$orderBy = \Input::get('q');
return \Products::lists('name', 'id')->orderBy($orderBy);
}
For outside of a framework just change the url to your php file and add in a data attribute for the method you require to be fired from the file.
$.post( 'products.php', // hit our route
{
action: 'order',
q: str,
},
...
Then in products.php you'd do something like this
if(isset($_POST['action']) && !empty($_POST['action'])) {
$action = $_POST['action'];
switch($action) {
case 'order' : order();break;
case 'otherFunction' : otherFunction();break;
}
}
function order()
{
// order logic here
// get $_POST['q']
// do your ordering
// return data as json
}
See here for similar question: using jquery $.ajax to call a PHP function

Zend Framework 2: Cannot attach to 'dispatch' event

I am currently learning/experimenting with the stable version of ZF2. The last couple of days has been used trying to find a solution to my problem, which is: I want to be able to write some setup logic general to a set of controllers. In ZF! I would then just write a general controller and derive from it, using the init() method for my setup logic. After a bit of searching I found that the init() method was removed in ZF2 and that there were alternative approaches to get the same functionality.
I tried to follow the guide by M. W. O'Phinney: http://mwop.net/blog/2012-07-30-the-new-init.html
In my case I have to be able to retrieve and check route params for my setup logic, so the method overriding alternatives didn't work due to one not having access to the MvcEvent at that point. So, I tried the Update: serviceManager solution, and this is where I got stuck. First I just tried to copy the code from the guide into my Module class and echo some text to see if the callback was at all called. Which it isn't.
After more searching on the web i found a possible solution; attaching the callback in the constructor of the general controller. The same problem appeared to be here as well. The constructor gets called of course, but the callback is either not attached or triggered properly (or at all).
I'll attach some of my code from the two different solutions:
In Module.php:
public function getControllerConfig() {
return array(
'factories' => array(
'Game\Controller\Mapsquare' => function($controllers) {
$serviceManager = $controllers->getServiceLocator();
$eventManager = $serviceManager->get('EventManager');
$controller = new Controller\MapsquareController();
echo "this text is echoed";
$eventManager->attach('dispatch', function ($e) use ($controller) {
echo "this text is NOT echoed";
$request = $e->getRequest();
$method = $request->getMethod();
if (!in_array($method, array('PUT', 'DELETE', 'PATCH'))) {
// nothing to do
return;
}
if ($controller->params()->fromRoute('id', false)) {
// nothing to do
return;
}
// Missing identifier! Redirect.
return $controller->redirect()->toRoute(/* ... */);
}, 100); // execute before executing action logic
$controller->setEventManager($eventManager);
return $controller;
}
)
);
}
In MapsquareController.php (the general controller):
public function __construct() {
$this->getEventManager()->attach('dispatch', array($this, 'preDispatch'), 1000);
echo "construct";
}
public function preDispatch() {
echo "This is preDispatch()!";
}
Is there someone out there that can help me with this problem, eventually tell what I'm missunderstanding here? Any help is appreciated :)
You cannot attach to dispatch from a factory because factory is called a long time after the dispatch has been processed.
To make it work, open your Module.php and edit onBootstrap(), so that you attach there.
For example:
public function onBootstrap($e)
{
...
$eventManager = $e->getApplication()->getEventManager();
$eventManager->attach("dispatch", function($e) {
echo "Dispatch!";
});
}
Alternatively, you can also do this from a specific controller. Not in the constructor, but by overriding setEventManager:
public function setEventManager(EventManagerInterface $events) {
parent::setEventManager($events);
$controller = $this;
$events->attach("dispatch", function($e) use ($controller) {
echo "Dispatch!";
});
}
Hope this helps!
Pls note that the accepted is not correct. The code posted by OP is simply not functional. Never polute your onBootstrap with cross-cutting concerns. The reason this:
public function __construct() {
$this->getEventManager()->attach('dispatch', array($this, 'preDispatch'), 1000);
echo "construct";
}
didn't work is because the event manager gets attached AFTER construction, and fires on dispatch.
The comment about "factory is called a long time after the dispatch" makes no sense also. Do you know what a factory does?
The suggestion by "Mr. O'phinney" is the correct way, maybe read the manual better next time before you decide someone like Matthew disappointed you..
Solution is here: http://mwop.net/blog/2012-07-30-the-new-init.html
Evan Coury's post on Module-specific layouts in Zend Framework 2 seems to give a solution, it works for me.
http://web.archive.org/web/20140623024023/http://blog.evan.pro/module-specific-layouts-in-zend-framework-2

Post multiple parameters to MVC Controller using jQuery.post

I have a controller defined as:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult PostMoreData(DataContracts.Address address, DataContracts.GeoLocation geoLocation)
{
return Json("test");
}
where DataContracts.Address and DataContracts.GeoLocation are complex types.
From my View i'm trying to post using jQuery as such:
function PostMoreData() {
var JsonAddress = {
"Building": $('Building').val(),
"UnitNumber": $('UnitNumber').val(),
"StreetNumber": $('StreetNumber').val(),
"StreetName": $('StreetName').val(),
"StreetType": $('StreetType').val(),
"Suburb": $('Suburb').val(),
"State": $('State').val(),
"Postcode": $('Postcode').val(),
"MonthsAtAddress": $('MonthsAtAddress').val()
};
var JsonGeoLocation = {
"Latitude": $('Latitude').val(),
"Longitude": $('Longitude').val()
};
jQuery.post("/AddressValidation/PostMoreData", {address: JsonAddress, geoLocation: JsonGeoLocation}, function(data, textStatus) {
if (textStatus == "success") {
var result = eval(data);
if (result.length > 0) {
alert(result);
}
}
}, "json");
}
However, on the controller, I get nulls.
It works if my Controller takes just 1 argument and I post just one object.
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult PostMoreData(DataContracts.Address address)
{
return Json("test");
}
function PostMoreData() {
var JsonAddress = {
"Building": $('Building').val(),
"UnitNumber": $('UnitNumber').val(),
"StreetNumber": $('StreetNumber').val(),
"StreetName": $('StreetName').val(),
"StreetType": $('StreetType').val(),
"Suburb": $('Suburb').val(),
"State": $('State').val(),
"Postcode": $('Postcode').val(),
"MonthsAtAddress": $('MonthsAtAddress').val()
};
jQuery.post("/AddressValidation/PostMoreData", JsonAddress, function(data, textStatus) {
if (textStatus == "success") {
var result = eval(data);
if (result.length > 0) {
alert(result);
}
}
}, "json");
}
Any ideas how i can post more than one object?
Note that the "default serialization" that jQuery is doing here isn't going to work no matter what your controller does. jQuery doesn't "traverse" the parameter map below the first level, so the example in the question is likely generating this post data:
address=[object]&geoLocation=[object]
The other, working example does not contain any sub-objects, so it is being translated directly, like this:
Building=value&UnitNumber=value&...&MonthsAtAddress=value
The easiest fix is making the parameter map flat, each key prefixed with either 'Address.' or 'GeoLocation.', depending.
Thank you everyone for your input on this issue.
At this stage, we have departed from using jquery to post complex types to controllers. Instead we use the ms ajax framework to do that. ms ajax post nicely binds the complex types automatically out of the box.
So our solution now uses a mix of jquery and ms ajax framework.
Ash
Your code requires that the way jquery serializes an object is compatible with the MVC default model binder, which I think is unlikely.
If you can build your javascript object so that it serializes as a flat object with dot notation (JsonAddress.Building) that would work, or you can let jquery do the default serialization and then create a custom model binder to deserialize to the action parameter types.
I had the same problem and couldn't get anything to work. Also someone raised it as a bug with jquery and they closed it as not a bug.
I have found a few solutions which answer part of the whole question.
And the answer includes the following.
1) Client side: we would need to stringyfy all the objects you need to send. This could be a normal object or an array. It works on both.
2) Client side: You send the data as you have in the first post. As you would object by object.
Tip: When you send parameterised objects, jquery encodes the data sent to the server.
Following all are server side implementations
1) Deserializer class: which will take the input string and put it back in to object/list<>/IList<> what ever you have defined as datatype of the parameter of the controller function.
You would need to implement ActionFilterAttribute for the above.
2) Finally add an attribute to controller function, so that it uses the deserialiser class to get the parameters.
As this is quite a lot of code let me know if you need details or have you solved the problem.
Deepak Chawla

Resources