How can you add dynamic ColdBox routes after configuration loads? - coldfusion

I'd like to create some routes based on data in a database table. I assume this will need to be done after the framework initializes so what I've done so far is created a new interceptor that runs on: afterConfigurationLoad.
My interceptor receives a service from my model which will query the data I need to create the routes. However, I'm not sure how to add routes at this point. When I try and call addRoute() directly I get an error Variable ADDROUTE is undefined. so I guess addRoute() doesn't exist in the framework Supertype.
Here's some sample code from my interceptor:
component {
property name="myService" inject="myService"; // injects my model which will get some data
/**
* afterConfigurationLoad
* Runs after the framework configucation loads
*/
void function afterConfigurationLoad( event, interceptData, buffer, rc, prc ){
// get some data which will be converted into routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// add our route. NOTE: I'll also want to populate the prc scope with a value
addRoute( pattern="/#data.slug#", handler="data", action="index" );
}
}
}
Edit 1: Using ColdBox v5.1.1
Update 1:
Brad's answer put me on the right track. I was unable to inject the router in my interceptor because it generated an error. However, I was able to get an instance of the router from within the function using getInstance( "router#coldbox" ) and then call route() methods as needed to create my routes.
Important: Routes by default are appended to the routing table. You will likely need to prepend the routes if you want them to work.
Here's an updated version of the code which will work:
Solution 1:
component {
property name="myService" inject="myService"; // injects my model which will get some data
/**
* afterConfigurationLoad
* Runs after the framework configucation loads
*/
void function afterConfigurationLoad( event, interceptData, buffer, rc, prc ){
// instance the router
var router = getInstance( "router#coldbox" );
// get some data which will be converted into routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// prepend our route
router.route( "/#data.slug#" ).prcAppend( { id : #data.id# } ).to( "data.index" ).prepend();
}
}
}
Additionally, there may be a simpler way to solve this problem by simply injecting the model service into the `config/router.cfc' configuration file and adding any dynamic routes that way.
Solution 2:
component {
property name="myService" inject="myService"; // inject the model
function configure() {
setFullRewrites( true );
// get the data I need from the model for dynamic routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// add the dynamic route
router.route( "/#data.slug#" ).prcAppend( { id : #data.id# } ).to( "data.index" );
}
route( ":handler/:action?" ).end();
}
}
Solution 3:
As it turns out, when the framework initializes, the interceptors get loaded first so not all dependencies will be available yet. Brad suggested using the provider namespace in the property which should also be an acceptable solution.
component {
property name="myService" inject="myService"; // injects my model which will get some data
property name="router" inject="provider:router#coldbox";
/**
* afterConfigurationLoad
* Runs after the framework configucation loads
*/
void function afterConfigurationLoad( event, interceptData, buffer, rc, prc ){
// get some data which will be converted into routes
var myDataList = myService.list();
// loop through the data
for ( var data in myDataList ) {
// prepend our route
router.route( "/#data.slug#" ).prcAppend( { id : #data.id# } ).to( "data.index" ).prepend();
}
}
}

Inject the ColdBox Router
property name='router' inject='router#coldbox';
and call its methods detailed here in the API docs:
http://apidocs.ortussolutions.com/coldbox/5.2.0/index.html?coldbox/system/web/routing/Router.html
The addRoute() method is part of that CFC.

Related

How to catch a form when it's submitted and forward form values to Dotmailer?

I've been asked to make a change to a Drupal 8 site (I'm not a Drupal Developer), the client would like an enquiry form linking up with Dotmailer. Within the theme I've tried to create a handler based on some information that I found online. But I don't know if it's doing anything. I've checked the logs section of Drupal and there isn't anything logged to indicate that the custom handler was actioned.
My theme is named abc-primary, and inside the theme folder I have created the file abc_primary.theme with the following contents;
<?php
use Drupal\media\Entity\Media;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_form_system_theme_settings_alter().
*/
function abc_primary_form_system_theme_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$theme_file = drupal_get_path('theme', 'abc_primary') . '/abc_primary.theme';
$build_info = $form_state->getBuildInfo();
if (!in_array($theme_file, $build_info['files'])) {
$build_info['files'][] = $theme_file;
}
$form_state->setBuildInfo($build_info);
$form['#submit'][] = 'abc_primary_form_system_theme_settings_submit';
}
function abc_primary_form_system_theme_settings_submit(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
// TODO: Extra submission logic.
// This submit handler will be called before default submit handler for this form.
\Drupal::logger('mymodule')->notice('mymodule submit ') ;
}
Is the above correct or am I doing something wrong?
Create a new module, and define following method:
function mymodule_form_node_form_alter(&$form, FormStateInterface $form_state) {
//you may want to check your form_id by
// $form['#form_id']
$form['actions']['submit']['#submit'][] = 'mymodule_node_form_submit';
}
function mymodule_node_form_submit($form, FormStateInterface $form_state) {
// get values from form_state
//log or send to desired web service
}
Hope it helps.

Coldbox recommended way of getting access to variables set inside legacy application cfc

I am adding Coldbox to our legacy application and I ran into a problem where we can't access certain variables from within the views when using Coldbox. In the existing legacy code inside of Application.cfc in the onRequestStart method we set several variables like so:
VARIABLES.screenID = 0;
VARIABLES.DSN = 'datasourcemain';
VARIABLES.DSNRO = 'datasourcereadonly';
VARIABLES.DSNADMIN = 'datasourceadmin';
VARIABLES.pagetitle = "Default Page Title for web application";
This is just a small snippet of the variables set. The problem is that in the legacy code these were used all over the place like in the header and footer. When browsing to a legacy page these are still accessible but when sending the request through coldbox the variables become inaccessible. My question is, is there a recommended way that I can have Coldbox know about these variables and pass them along to the views so I don't have to modify hundreds of files?
It depends, there are a few places to define such variables. From the limited information given, I would suggest you add the datasource information to the Coldbox.cfc > datasources struct (#1) and add the default pageTitle to the global request handler (#2). As for the screenID, who knows -- good luck!
config/Coldbox.cfc has both a settings and datasources struct that can be injected via wirebox into the handlers/controllers.
// Dependency Injection using WireBox
property name='settings' inject='coldbox:settings';
Use a global request handler and add all the global vars into the prc (private request context) which is visible to the controller and views.
//config/Coldbox.cfc
...
coldbox = {
requestStartHandler: 'Main.onRequestStart'
};
...
// handlers/Main.cfc
component extends='coldbox.system.EventHandler' {
function onRequestStart( event, rc, prc) {
prc.screenID = 0;
prc.DSN = 'datasourcemain';
prc.DSNRO = 'datasourcereadonly';
prc.DSNADMIN = 'datasourceadmin';
prc.pagetitle = "Default Page Title for web application";
}
}
Use a request interceptor and add data to the prc.
//config/Coldbox.cfc
...
interceptors = [
{ class="interceptors.Globals" }
];
...
//interceptor/Globals.cfc
component {
property name='legacyGlobals' inject='LegacyGlobals';
function preProcess(event, interceptData) {
event.setPrivateValue('someLegacyGlobalVar', legacyGlobals.getSomeLegacyGlobalVar() );
}
}

Laravel 5 - global Blade view variable available in all templates

How can I in Laravel 5 make global variable which will be available in all Blade templates?
Option 1:
You can use view::share() like so:
<?php namespace App\Http\Controllers;
use View;
//You can create a BaseController:
class BaseController extends Controller {
public $variable1 = "I am Data";
public function __construct() {
$variable2 = "I am Data 2";
View::share ( 'variable1', $this->variable1 );
View::share ( 'variable2', $variable2 );
View::share ( 'variable3', 'I am Data 3' );
View::share ( 'variable4', ['name'=>'Franky','address'=>'Mars'] );
}
}
class HomeController extends BaseController {
//if you have a constructor in other controllers you need call constructor of parent controller (i.e. BaseController) like so:
public function __construct(){
parent::__construct();
}
public function Index(){
//All variable will be available in views
return view('home');
}
}
Option 2:
Use a composer:
Create a composer file at app\Composers\HomeComposer.php
NB: create app\Composers if it does not exists
<?php namespace App\Composers;
class HomeComposer
{
public function compose($view)
{
//Add your variables
$view->with('variable1', 'I am Data')
->with('variable2', 'I am Data 2');
}
}
Then you can attached the composer to any view by doing this
<?php namespace App\Http\Controllers;
use View;
class HomeController extends Controller{
public function __construct(){
View::composers([
'App\Composers\HomeComposer' => ['home'] //attaches HomeComposer to home.blade.php
]);
}
public function Index(){
return view('home');
}
}
Option 3:
Add Composer to a Service Provider, In Laravel 5 I prefer having my composer in App\Providers\ViewServiceProvider
Create a composer file at app\Composers\HomeComposer.php
Add HomeComposer to App\Providers\ViewServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use View;
use App\Composers\HomeComposer;
use Illuminate\Support\Facades\Blade;
class ViewServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//add to all views
view()->composer('*', HomeComposer::class);
//add to only home view
//view()->composer('home', HomeComposer::class);
}
}
Create a new Service Provider as suggested in here
Add your new Service Provider to the configuration file (config/app.php).
In the boot method of your new Service Provider use:
View::share( 'something_cool', 'this is a cool shared variable' );
Now you are ready to use $something_cool in all of your views.
Hope this helps.
Searching for solution of the same problem and found the best solution in Laravel documentation. Just use View::share in AppServiceProvider like this:
View::share('key', 'value');
Details here.
You can do this with view composers. View composers are executed when a template is loaded. You can pass in a Closure with additional functionality for that view. With view composers you can use wildcards. To make a view composer for every view just use a *.
View::composer('*', function($view)
{
$view->with('variable','Test value');
});
You can also do this without a closure as you can see in the docs.
View::composer('*', 'App\Http\ViewComposers\ProfileComposer');
The profile composer class must have a compose method.
View composers are executed when a view is rendered. Laravel has also view creators. These are executed when a view is instantiated.
You can also choose to use a BaseController with a setupLayout method. Then every view which you will load is loaded through the setupLayout method which adds some additional data. However, by using view composers you're pretty sure that the code is executed. But with the BaseController approach you've more flexibility because you can skip the loading of the extra data.
EDIT: As mentioned by Nic Gutierrez you can also use view share.
Also, you can do this in the Route.php file:
view()->share('variableName', $variable);
I would rather use middleware with the view() facade helper. (Laravel 5.x)
Middleware is easier to mantain and does not make a mess in the controllers class tree.
Steps
Create the Middleware
/app/Http/Middleware/TimezoneReset.php
To create a middleware you can run php artisan make:middleware GlobalTimeConfig
share() the data you need shared
<?php
namespace App\Http\Middleware;
use Closure;
class GlobalTimeConfig
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$time_settings = [
'company_timezone' => 'UTC',
'company_date_format' => 'Y-m-d H:i:s',
'display_time' => true,
];
view()->share('time_settings', $time_settings);
return $next($request);
}
}
Register the newly created middleware
Add the middleware to your middleware route group as per example below
/app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\GlobalTimeConfig::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
Access data from templates
Access the data from any template with the given key in the View::share() method call
eg.:
Company timezone: {{ $time_settings['company_timezone'] }}
EDIT:
Nic Gutierrez's Service Provider answer might be a better (or the best) solution.
and you can give array not just View::share('key', 'value');
can put array like View::share(['key'=>'value','key'=>'value'])
You can add in Controller.php file:
use App\Category;
And then:
class Controller extends BaseController {
public function __construct() {
$categories = Category::All();
\View::share('categories', $categories);
}
}
you can flash it into the session, you can define it in the .env file (static vars)

OpenCart pass data to children module

Basically what i'm trying to do is to pass some data from "parent" controller to the controller of its children module, for example:
header controller
$this->children = array(
'module/newslettersubscribe'
);
newslettersubscribe controller
public function index() {
// Use here data from the header controller
}
Is that even possible to do?
Here the approach is incorrect. You should edit only the slider controller and check whether there is a path variable available in the GET (query string), e.g.:
if (!empty($this->request->get['path'])) { /* ... */ }
If it is you can now extract the categories from it's value (which is e.g. 1_12_36):
if (!empty($this->request->get['path'])) {
$category_ids = explode('_', (string)$this->request->get['path']);
}
Now knowing the category IDs (or the category path) you can display the appropriate images only using whatever code you made up.

Refresh all data other than session data when context changes

I'm writing a small sample application which will display some information to a user in dashboards and tables.
The application is being written using ember-cli version 0.0.37. and ember.js version 1.5.1.
I am using ember-simple-auth and ember-simple-auth-oauth2 for authentication, with a custom authenticator and authroizer to inject the client_id, client_secret and access_token into the requests where appropriate (I am fully aware that the client secret shouldn't be in a front end application, but this is a sample application for internal consumption only and not the subject of my question).
The custom authorizer also injects an o parameter into the requests, the value of which is an organisation id. The API returning data uses both the access_token and the o parameter to return data pertaining to a particular organisation. The organisation id is stored in the session.
So once I've browsed around for one organisation, I've got a dropdown component which allows me to choose another organisation. At present, this calls an action on the ApplicationRoute which updates the value in the session, and clears the store of all models that are not account or organisation, as these are used at the application level.
Code:
setSessionOrganisation: function(organisation_id) {
var self = this;
/**
* Check if its the same organisation
*/
if (this.get('session.organisation_id') == organisation_id) {
return;
}
/**
* Get the organisation for the id provided
*/
this.store.find('organisation', organisation_id).then(
function(org) {
/**
* Update the session details
*/
self.set('session.organisation_id', org.get('id'));
self.set('session.organisation_name', org.get('name'));
/**
* Get all types
*/
var store = self.container.lookup('store:main');
var types = [];
var typeMaps = store.typeMaps;
$.each(typeMaps, function(key) {
if (typeMaps.hasOwnProperty(key)) {
var type = typeMaps[key].type.typeKey;
if (type != 'account' && type != 'organisation'){
types.push(typeMaps[key].type.typeKey);
}
}
});
/**
* Clear data for types
*/
for (var i = 0; i < types.length; i++) {
store.unloadAll(types[i]);
};
});
}
I feel the code above is a bit hackish, but I've not found another way to return the model types currently in the store.
When this action is called, and the data has been flushed, I would like to then refresh/reload the current route. Some of the routes will be dynamic, and using the object ids as the dynamic segment. These routes will need to redirect to their list routes. Other routes will carry on lazily loading data when they are navigated to.
So my questions are:
Is there a better way to clear the store data than I have done above?
How can I trigger a route reload?
How can I redirect to the parent route if in a route with a dynamic segment?
As a bonus question, are there any dangers in unloading all the data for views/routes that are presently not being displayed?
Thanks!
I've come to a resolution that satisfies my criteria, with great help from #marcoow
As per the comments on the question, we discussed reloading the entire application to refresh the data store cache. While this approach does work, there was a flicker while the browser completely reloaded the page, and I would prefer to only reload the data and have the application sort itself out. This flicker was also present if App.reset() was called.
I explored other options and found the Ember.Route.refresh() method. This appears to cause the nested routes to reload their models when called from the application route.
In my application I have extended the store in an initializer, so that I can call a function which unloads all records from the store, for every type of model in the store, but also provide a list of model names to exclude:
app/initializers/custom-store.js:
import DS from 'ember-data';
var CustomStore = DS.Store.extend({
/**
* Extend the functionality in the store to unload all instances of each type
* of data except those passed
*
* #param {Array} exclusions List of model type names to exclude from data
* unload
*/
unloadAllExcept: function(exclusions) {
var typeMaps = this.get('typeMaps');
for (var type in typeMaps) {
if (typeMaps.hasOwnProperty(type)) {
var typeKey = (typeMaps[type].type && typeMaps[type].type.typeKey) ? typeMaps[type].type.typeKey : false;
if (typeKey && exclusions.indexOf(typeKey) === -1) {
this.unloadAll(typeKey);
}
}
}
}
});
export
default {
after: 'store',
name: 'custom-store',
initialize: function(container /*, app*/ ) {
container.unregister('store:main');
container.register('store:main', CustomStore);
}
};
This is called from app/routes/application.js in the setSessionOrganisation Action:
setSessionOrganisation: function(organisation_id) {
var _this = this;
/**
* Check if its the same organisation
*/
if (this.get('session.organisation_id') === organisation_id) {
return;
}
/**
* Get the organisation for the id provided
*/
this.store.find('organisation', organisation_id)
.then(
function(org) {
/**
* Update the session details
*/
_this.get('session')
.set('organisation_id', org.get('id'));
_this.get('session')
.set('organisation_name', org.get('name'));
/**
* Clean the local data cache of all data pertaining to
* the old organisation
*/
_this.store.unloadAllExcept(['account', 'organisation']);
/**
* refresh the application route (and subsequently all
* child routes)
*/
_this.refresh();
});
}
As a footnote, this solution does not satisfy the third question I asked, but I have realised that question is a separate issue to do with handling the response from the API, and must be dealt with during the model hook.