Sf2: Same url, two controllers depending on device - symfony

Let's say that we have homepage under
http://foo.bar/
and controller that handles it:
class HomepageController extends FooController{
public function homepageAction()
{
(some stuff)
}
}
Homepage was desktop only, then it went RWD. Next step was writing separate views for desktop and mobile and wiring them up like this (simplified).
class HomepageController extends FooController{
public function homepageAction()
{
(some stuff)
if(true === $this->detector->isMobile())
{
return new Response($this->renderView('homepage.mobile.html.twig' [
'some' => $vars',
]
));
}
return new Response($this->renderView('homepage.mobile.html.twig' [
'some' => $vars',
'only' => $for,
'desktop' => $version,
]
));
}
}
Now is the next step - make mobile version of homepage run on different controller. This is because desktop version gets so many data that is not required in mobile, causing current flow to be not optimized.
So, to sum it up, conditions:
mobile detector is already in project declared as a service,
homepage address cannot differ from desktop version,
desktop and mobile versions should run on separate controllers.
So far, only solution that I've found is to:
1. always hit desktop controller,
2. first action to do is to check if request comes from mobile,
3. if yes, then forward internally to mobile homepage controller.
Sounds good, but I believe it can be done better!
I have another two theories about how do to that, but I don't want to propose them right now so noone gets influenced.

Related

Creating a NavigationStrategy in Aurelia

I'm trying to use Aurelia as my platform for a custom Wordpress theme.
What I want to do is define my navigation menu in Wordpress, use the Wordpress plugin for menus to expose the menus through the Wordpress API as a JSON string and then building the navigation menu in Aurelia.
Everything I have found so far involves creating a simple one line menu.
Has anyone done this or can point me in the right direction?
Since you're using server-side data to build your navigation menu, you might as well let the server do the hard work and let it generate a ready-to-use configuration for your router.
Let your plugin generate JSON like this:
{
"routes": [
{
"route": "\"...\"",
"moduleId": "\"...\"",
"settings": {
"childRoutes": [
{
"route": "\"...\"",
"moduleId": "\"...\"",
}
]
}
]
}
Then in your root view model's configureRouter you could do something like this:
async configureRouter(config, router) {
const resp = await http.fetch("api/menu-plugin-uri");
const json = await resp.json();
config.map(json.routes);
}
The child routes are stored in the settings object of the config, meaning we can use it for building a navigation menu and we can access it in child routes like so:
configureRouter(config, router) {
const parentConfig = router.parent.currentInstruction.config;
config.map(parentConfig.childRoutes);
}
That doesn't give you nice NavModels with isActive and everything, but this is about as good as it gets when it comes to nested navigation menu's currently.
I'm actually working on a plugin myself to try and address some of these limitations, though NOT ready for production yet.

Structuring a reducer for a simple CRUD application in redux

So I'm creating what is at it's core a very simple CRUD-style application, using React + Redux. There is a collection of (lets call them) posts, with an API, and I want to be able to list those and then when the user clicks on one, go into a detail page about that post.
So I have a posts reducer. Originally I started using the approach taken from the redux real-world example. This maintains a cache of objects via an index reducer, and when you do a "get post" it checks the cache and if it's there, it returns that, else it makes the appropriate API call. When components mount they try to get things from this cache, and if they're not there they wait (return false) until they are.
Whilst this worked OK, for various reasons I now need to make this non-caching i.e. everytime I load the /posts/:postId page I need to get the post via the API.
I realise in the non-redux world you would just do a fetch() in the componentDidMount, and then setState() on that. But I want the posts stored in a reducer as other parts of the app may call actions that modify those posts (say for example a websocket or just a complex redux-connected component).
One approach I've seen people use is an "active" item in their reducer, like this example: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/reducers/reducer_posts.js
Whilst this is OK, it necessitates that each component that loads the active post must have a componentWillUnmount action to reset the active post (see resetMe: https://github.com/rajaraodv/react-redux-blog/blob/master/public/src/containers/PostDetailsContainer.js). If it did not reset the active post, it will be left hanging around for when the next post is displayed (it will probably flash for a short time whilst the API call is made, but it's still not nice). Generally forcing every page that wants to look at a post to do a resetMe() in a componentWillUnmount fells like a bad-smell.
So does anyone have any ideas or seen a good example of this? It seems such a simple case, I'm a bit surprised I can't find any material on it.
How to do it depends on your already existing reducers, but i'll just make a new one
reducers/post.js
import { GET_ALL_POSTS } from './../actions/posts';
export default (state = {
posts: []
}, action) => {
switch (action.type) {
case GET_ALL_POSTS:
return Object.assign({}, state, { posts: action.posts });
default:
return state;
}
};
It is very easy to understand, just fire an action to get all your posts and replace your previous posts with the new ones in the reducer.
Use componentDidMount to fire the GET_ALL_POSTS action, and use a boolean flag in the state to know if the posts where loaded or not, so you don't reload them every single time, only when the component mounts.
components/posts.jsx
import React from 'react';
export default class Posts extends React.Component {
constructor(props) {
super(props);
this.state = {
firstLoad: false
};
}
componendDidMount() {
if (!this.state.firstLoad) {
this.props.onGetAll();
this.setState({
firstLoad: true
});
}
}
// See how easy it is to refresh the lists of posts
refresh() {
this.props.onGetAll();
}
render () {
...
// Render your posts here
{ this.props.posts.map( ... ) }
...
}
}
We're just missing the container to pass the posts and the events to the component
containers/posts.js
import { connect } from 'react-redux';
import { getPosts } from './../actions/posts';
import Posts from './../components/posts.jsx';
export default connect(
state => ({ posts: state.posts }),
dispatch => ({ onGetAll: () => dispatch(getPosts()) })
);
This is a very simple pattern and I've used it on many applications
If you use react-router you can take advantage of onEnter hook.

Best (default) approach for aurelia databinding/gui element initialization

I'm playing around with Aurelia.js in combination with semantic-ui.
Both framework have abilities to fill for instance "select" elements of html.
(Following the 2 "offical examples".)
The way of semantic would for instance be:
(<any>$('#semanticSelect'))
.dropdown({
apiSettings: {
url: '//api.semantic-ui.com/tags/{query}'
}
})
;
The way of Aurelia according to the sample would be with httpclient
users = [];
constructor(private http: HttpClient) {
http.configure(config => {
config
.useStandardConfiguration()
.withBaseUrl('https://api.github.com/');
});
}
activate() {
return this.http.fetch('users')
.then(response => response.json())
.then(users => this.users = users);
}
and in html with
repeat.for="user of users"
and binding according to needs like
<div class="item" repeat.for="user of users" data-value.bind="user.id">${user.login}</div>
So now I'm a little bit confused what's the correct way to work with? Can anyone explain me what's the difference and what's the recommed way?
It's a primary question how GUI controls should be initialized -> by aurelia framwork methods or of semantic ui framework! I'm considering about performance, caching and safety. I have nowhere read how it's recommed to do.
Thanks!
If you are going to use a full framework like Aurelia, I would suggest to just use the Aurelia method since semantic-ui is only for view layout and css with some javascript components.

Regular views don't work when we are on iPhone?

I use MVC 4 with this custom displaymodeprovider. Even if I set it as "false" it still on iPhone returns mobile version, though I want to return regular version of the website. Please help
DisplayModeProvider.Instance.Modes.Insert(0, new
DefaultDisplayMode("Mobile")
{
ContextCondition = (context => false)
});
I set the break point inside this this code and it calls, though it still return mobile version.
Eventually I've figured out the problem
MVC 4 already has his own redirection for "Mobile" prefix. so to use custom logic we need to remove that DisplayModeProvider for "Mobile" prefix, like this:
var mobileModel = DisplayModeProvider.Instance.Modes.FirstOrDefault(a => a.DisplayModeId == "Mobile");
if (mobileModel != null)
{
DisplayModeProvider.Instance.Modes.Remove(mobileModel);
}
DisplayModeProvider.Instance.Modes.Insert(0, new
DefaultDisplayMode("Mobile")
{
ContextCondition = (context => <USE ANY YOUR CUSTOM LOGIC>)
});
This answer will be very useful for people who want that their website will be available in both versions for iPad or iPhone or Android and we can for example store in coockie user selection which version of the website we should display in his device.

Orchard Custom Widget with Form

I built a custom module that manages appointments for a service-based company. All of the current functionality is contained in the admin section. I have not used a single ContentItem or ContentPart. All the models are just plain records.
I'm looking to create a widget to expose the ability to sign up for an appointment from the front end. I have a partial view and a controller that handles the display and form submit, but I'm not sure how to tie that into a widget that can be placed in one of the content zones of the front-end.
I've spent quite a bit of time researching this, and can't find a good path to follow. (I've tried a few and got sub-optimal results)
Any suggestions?
The best answer for me was to create a widget Type definition in the migration.cs file of the module:
ContentDefinitionManager.AlterTypeDefinition("CreateAppointmentWidget",
cfg => cfg
.WithPart("WidgetPart")
.WithPart("CommonPart")
.WithSetting("Stereotype", "Widget"));
Then create a handler for that widget at /MyModule/Handlers/CreateAppointmentWidgetHandler.cs:
public class CreateAppointmentWidgetHandler : ContentHandler
{
private readonly IRepository<FieldTechRecord> _repository;
public CreateAppointmentWidgetHandler(IRepository<FieldTechRecord> repository)
{
_repository = repository;
}
protected override void BuildDisplayShape(BuildDisplayContext context)
{
base.BuildDisplayShape(context);
if (context.ContentItem.ContentType == "CreateAppointmentWidget")
{
CreateAppointmentViewModel model = new CreateAppointmentViewModel(_repository.Fetch(x => x.IsActive));
context.Shape.AppointmentModel = model;
}
}
}
Then create a matching widget template at /MyModule/Views/Widget-CreateAppointmentWidget.cshtml that inserts the Partial View:
#Html.Partial("CreateAppointment", (MyModule.Models.Views.CreateAppointmentViewModel)Model.AppointmentModel)
The above code grabs the partial view /MyModule/Views/CreateAppointment.cshtml.
Thanks to Giscard's suggestion, I was able to correct the links rendered from CreateAppointment.cshtml by using #Url.RouteUrl() and defining named routes to point where I needed the action and ajax requests to go.
The nice thing about this solution is that it provided a way to create the widget without having to rework my models to use Orchards ContentPart functionality.
Something is not connecting in my head, because I have been able to create a theme with zones, and then dispatch a shape from my module into that zone without much more than doing #Display.Shape(). So I am curious if it's absolutely necessary to use a handler to override the BuildDisplayShape.
Again, this is in the scenario where you have models as plain records (not using ContentItem or ContentPart - and even if not using them, you've shown an example of creating one through migrations).
Something like this - Controller:
public ShapeResult MyShape()
{
var shape = _orchardServices.New.MyPath1_MyShape();
return new ShapeResult(this, shape);
}
Then create a MyShape.cshtml shape with whatever code I have (no need for example).
NOTE: I use a custom IShapeTemplateHarvester file which adds paths where I can store my shapes (instead of using "Views", "Views/Items", "Views/Parts", "Views/Fields", which is the stock in Orchard). It goes something like this:
NB: I hate that code doesn't automatically wrap in SO.
[OrchardSuppressDependency("Orchard.DisplayManagement
.Descriptors.ShapeTemplateStrategy.BasicShapeTemplateHarvester")]
public class MyShapeTemplateHarvester : BasicShapeTemplateHarvester,
IShapeTemplateHarvester
{
public new IEnumerable<string> SubPaths()
{
var paths = base.SubPaths().ToList();
paths.Add("Views/MyPath1");
paths.Add("Views/MyPath2");
return paths;
}
}
Say I have Index.cshtml in my Theme. I have two choices (I use both and use the Theme as the default presentation).
Index.cshtml in Theme folder:
#*Default Content*#
Index.cshtml in Module folder:
#*Special Content overriding Theme's Index.cshtml*#
Display.MyPath1_MyShape()
Even better for me is that I can do this in the Index.cshtml in Theme folder:
#*Whatever content*#
Display.MyPath1_MySecondShape()
There is no ~/MyPath1/MySecondShape.cshtml in the Theme, but there is one in the Module, which the Theme displays! This is great because I can have a special Theme and have multiple modules (that are placed on separate sites) go back and forth with the theme (think Dashboard for different services in the same profession on different sites).
NOTE: The above may only be possible with IThemeSelector implementation such as:
public class MyThemeSelector : IThemeSelector
{
public ThemeSelectorResult GetTheme(RequestContext context)
{
if (MyFilter.IsApplied(context))
{
return new ThemeSelectorResult { Priority = 200,
ThemeName = "MyDashboard" };
}
return null;
}
}
Just my two bits.

Resources