My project has more than one layout, I want to make a folder to contain these layouts and pull them out of the view folder, but when I do this, the layout does not load when I run the application.
How could I solve this?
Tx in advance.
To solve this you have two options, depending on whether you are using routes or the controller to serve your views.
Firstly, let's say you have a folder "mylayouts" in your views folder and this folder contains numerous layouts eg. "layout_home.ejs", "layout_admin.ejs" etc.
To use these layouts for a specific view, you can:
A. Set the layout using locals in the config/routes.js file, be sure to include both the folder name and layout filename. eg.
'/': {
view: 'homepage',
locals: {
layout: '/mylayouts/layout_home.ejs'
}
}
B. Set the layout in the response from the controller, again insuring to include both the folder name and layout filename. eg.
/**
* AdminController
*
* #description :: Server-side logic for managing admins
* #help :: See http://sailsjs.org/#!/documentation/concepts/Controllers
*/
module.exports = {
index : function(req, res){
res.view({
layout : '/mylayouts/layout_admin.ejs'
})
}
};
Related
In our AEM 6.2 project, I ran to a scenario where I need to config a navigation in one page (let call this Homepage), all of the other pages can use home navigation config or use their own navigation config values.
I decided to use live copy because the clone pages can cancel the linked properties at any time and use their own values. But there is two problem with this approach:
Users must set the template for clone pages by edit their jcr: content/sling:resourceType and jcr: content/cq: template because the navigation is used in all pages and our web uses about 5+ templates.
Live copy does no allowed clone pages are children of source pages. But I was required to make web structure like this :
Home
|_ Page 1
|_ Page 1.1
|_ Page 2
|_ Page 3
Live copy maybe not suitable for this situation, we change to use HTL ${inheritedPageProperties}, this solve the template and structure issue but it creates two new problems:
Inherited properties in property config dialog of child page will be blank (because they are not set and called via ${inheritedPageProperties} )
If users change properties in "Page 1" page, "Page 1.1" (and Page 1.1.1, etc ...) will use these values (Because ${inheritedPageProperties} search the upper nodes to get value).
What our client want are :
All page can use navigation setting only from the homepage or their own page (use home page by default).
If use homepage properties, these values must show in their config dialog.
Try to avoid config template in CRXDE Lite
The website must have the parent-child structure
How can I achieve these requirements?
You can achieve this with a simple Sling Model and Sling's CompositeValueMap
CompositeValueMap docs state:
An implementation of the ValueMap based on two ValueMaps: - One containing the properties - Another one containing the defaults to use in case the properties map does not contain the values. In case you would like to avoid duplicating properties on multiple resources, you can use a CompositeValueMap to get a concatenated map of properties.
We can use this by supplying the descendant's value map (the current page's) then finding the correct ancestor and supplying its properties valuemap as the defaults.
for the purposes of this question, I always assume that 2rd descendant from root is always the ancestor (you can find your ancestor according to your requirnments)
package com.sample.helpers;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.CompositeValueMap;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.Self;
import javax.annotation.PostConstruct;
#Model(adaptables = Resource.class)
public class CustomInheritedPageProperties
{
ValueMap inheritedProperties;
#Self
Resource currentResource;
#OSGiService
PageManager pageManager;
#PostConstruct
protected void init() {
// get the current page, or the "descendant"
Page descendant = pageManager.getContainingPage(currentResource);
/* You have to add your custom logic to get the ancestor page.
* for this question's purposes, I'm always assuming it's the 3rd decendant of root
* more here: https://helpx.adobe.com/experience-manager/6-2/sites/developing/using/reference-materials/javadoc/com/day/cq/wcm/api/Page.html#getParent(int)
*/
Page ancestor = descendant.getParent(2);
// create a CompositeValueMap where the properties are descendant's and the defaults are ancestor's
inheritedProperties = new CompositeValueMap(descendant.getProperties(), ancestor.getProperties());
}
public ValueMap getInheritedProperties()
{
return inheritedProperties;
}
}
Now you can use this as follows
<sly data-sly-use.propHelper="com.sample.helpers.CustomInheritedPageProperties">>/sly>
<!--/* someProp here refers to the property you wish to get (inherited, of course)*/-->
<h1>propHelper.inheritedProperties.someProp</h1>
In my solution I have at the root level a Controller, Views Folder each of these have a Home Folder with an Home Controller with Index method and the view folder with an index view (default MVC setup). At the root level I have introduced an Areas folder then in here I have created another Folder for the area then folders for controllers and views. Home controller with index method and views folder with index view.
it all builds fine, but I receive the following error:
AmbiguousActionException: Multiple actions matched. The following actions matched route data and had all constraints satisfied:
VisualJobs.Controllers.HomeController.Index (VisualJobs)
VisualJobs.Areas.Recruiter.Controllers.HomeController.Index
(VisualJobs) VisualJobs.Areas.Jobs.Controllers.HomeController.Index
(VisualJobs)
in my Configuration file I have:
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areaRoute",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Folder structure:
Areas -->Candidate-->Controllers
Shared
ViewModels
Views
Areas -->Recruiter-->Controllers
Shared
ViewModels
Views
Controllers
Views
It may be because you have not decorated your area controller classes with the Area attribute. Something like this.
[Area("Recruiter")]
public class MyController : Controller
{
...
}
You also need _ViewImports.cshtml and _ViewStart.cshtml in the Views folder of your area.
Say I wrote a blog app in Sails.js.
On every page in this app, there is a sidebar widget called "Recent Posts", where it lists the titles of the 5 most recent posts and clicking on them takes you to the post in question.
Because this sidebar widget is present on every page, it should be in layout.ejs. But, here we have a conflict - dynamic content is only supposed to be pulled from the database in the controller action for rendering a specific view.
This dynamic content isn't for a specific view, it's for the whole site (via layout.ejs).
By the conventions that I understand, I'd have to get that dynamic content data for the sidebar widget in every controller action that renders a view (otherwise I would get an undefined error when I attempt to call that local in my layout.ejs file).
Things I've tried / considered:
Load that dynamic content in every controller action that renders a view (this solution is very bad) and calling that dynamic content in layout.ejs as if it were a local for the specific view. This works fine, but goes against D.R.Y. principles and quite frankly is a pain in the ass to have to run the same query to the database in every controller action.
As per another similar stackoverflow question, create a new config (E.G. config/globals.js), load my dynamic content from my database into that config file as a variable, and then calling sails.config.globals.[variable_name] in my layout.ejs file. This also worked, since apparently config variables are available everywhere in the application -- but it 's a hacky solution that I'm not a fan of (the content I'm loading is simply the titles and slugs of 5 recent posts, not a "global config option", as the solution implies).
Run the query to get the dynamic content inside the .EJS file directly between some <% %> tags. I'm not sure if this would work, but even if it did, it goes against the separation of concerns MVC principle and I'd like to avoid doing this if at all possible (if it even works).
As per a lengthy IRC discussion # http://webchat.freenode.net/?channels=sailsjs, it was suggested to create a policy and map that policy to all my controllers. In that policy, query the database for the 5 most recent posts, and set them to the req.recentposts. The problem with this solution is that, while the recent posts data will be passed to every controller, I still have to pass that req.recentposts data to my view -- making it so I still have to modify every single res.view({}) in every action. I don't have to have the database query in every action, which is good, but I still have to add a line of code to every action that renders a view... this isn't D.R.Y. and I'm looking for a better solution.
So, what is the proper solution, without needing to load that dynamic content in every controller action (a solution that adheres to D.R.Y. is what I'm lookng for), to get some dynamic content available to my layout.ejs file?
In folder /config you should create a file express.js and add something like that:
module.exports.express = {
customMiddleware: function(app){
app.use(function(req, res, next){
// or whatever query you need
Posts.find().limit(5).exec(function(err, posts){
res.locals.recentPosts = posts;
// remember about next()
next();
});
});
}
}
Then just make some simple loop in your view:
<% for(var i=0; i<recentPosts.length; i++) { %>
<% recentPosts[i].title %>
<% } %>
Here are some links to proper places in documentation:
https://github.com/balderdashy/sails-docs/blob/0.9/reference/Configuration.md#express
and
https://github.com/balderdashy/sails-docs/blob/0.9/reference/Response.md#reslocals
I found out another way to do this. What I did was to create a service that could render .ejs files to plain html by simply taking advantage of the ejs library already in sails. This service could either be invoked by the controller, or even passed as a function in the locals, and executed from within the .ejs. The service called TopNavBarService would look like:
var ejs = require('ejs');
exports.render = function() {
/* database finds goes here */
var userInfo = {
'username' : 'Kallehopp',
'real_name' : 'Kalle Hoppson'
};
var html = null;
ejs.renderFile('./views/topNavBar.ejs', {'locals':userInfo}, function(err, result) { html = result; });
return html;
}
In the constroller it could look like:
module.exports = {
testAction: function (req, res) {
return res.view('testView', {
renderNavbar: TopNavBarService.render // service function as a local!
});
}
};
This way you can create your customized ejs-helper that could even take arguments (although not shown here). When invoked, the helper could access the database and render a part of the html.
<div>
<%- renderNavbar() %>
</div>
I am currently working on a project developed using Zend Framework, based on the structure of my web page design I have reached a point where I have to pass a small number of variables to my layout from each Controller/Action. These variables are:
<?php Zend_Layout::getMvcInstance()->assign('pageId', 'page1'); ?>
<?php Zend_Layout::getMvcInstance()->assign('headerType', '<header id="index">'); ?>
The reason for passing this information is firstly, I pass the page id as the multi column layout may change depending on the content being displayed, thus the page id within the body tag links the appropriate CSS to how the page should be displayed. Secondly I display a promotional jQuery slider only on the index page, but I need the flexibility to have it displayed on potentially multiple pages in case the wind changes and the client changes their mind.
My actual question: Is there a more appropriate method of passing this information to the Layout that I am overlooking?
I am not really questioning whether the information has to be sent, rather is there some Zend Framework feature that I have, in my haste, overlooked which would reduce the amount of repetitive redundant code which may very well be repeated in multiple Actions within the same controller?
You could turn that logic into an action helper than you can call from your controllers in a more direct way. You could also make a view helper to accomplish the same thing but view helpers usually generate data for the view rather than set properties.
// library/PageId.php
class Lib_PageId extends Zend_Controller_Action_Helper_Abstract
{
public function direct($title, $pageId, $headerType)
{
$view = $this->getActionController()->view;
$view->headTitle()->append($title);
$view->pageId = $pageId;
$view->headerType = $headerType;
}
}
In your controller actions you can now do this:
$this->_helper->PageId('Homepage', 'page1', 'index');
// now pageId and headerType are available in the view and
// Homepage has been appended to the title
You will also need to register the helper path in your Bootstrap like this:
protected function _initActionHelpers()
{
Zend_Controller_Action_HelperBroker::addPrefix('Lib');
}
Doing it like that can reduce the amount of repetitive code and remove needing to assign the values from the view. You can do it in the controller very quickly. You can also have default values in the case that the helper hasn't been called.
You shoudn't really be passing anything from the view to the layout, for a start the view should be included IN the layout, not the other way around.
So, setting your page title should be done using similar code to what you have, but inside the controller action being called:
$this->view->headTitle()->append('Homepage');
And the other two issues - you need to rethink as I stated to begin with. Maybe you're misunderstanding the layout/view principle? If you include the different views per action, then you simply change the div id when needed, and include the header for your banner only in the index.phtml file.
I'd like to have my controller - i.e. Page_IndexController - extend a base controller.
For example;
class Page_IndexController extends Content_IndexController {
}
However, it seems the autoloader doesn't pick up the fact it's a controller class at any point - I get the error Fatal error: Class 'Content_IndexController' not found
First question: How do I fix this?
I can temporarily fix this by require_once'ing the generic 'content' controller, but this is hardly ideal.
The next issue is that if my Page controller has it's own view script for an action, it works no problem.
But if I'm extending a controller, and I call for example 'listAction' on the Page controller, but this action is implemented in Content_IndexController, it still looks for the list view script in the page controllers "scripts" directory.
Second question: How do I configure my controller to use its parents view script if it doesn't have its own?
If your application can find Page_IndexController you probably have a Page module. If you are not using modules in your application you have to name your controllers PageController and ContentController, not Page_IndexController, ... So the solution is to register "Content_" namespace with the autoloader.
As for the second question. You can extend the provided ViewRenderer controller action helper and override methods for finding the view script so they can look in other places if needed. You just have to pass your viewrenderer to the front controller. For passing your own ViewRenderer to the controller take a look at Advanced Usage Examples.
The auto loader can't find your controller because you haven't told it where to search. The Content_IndexController isn't in your "library" folder (I assume its inside of the Content module)
What I would suggest is creating a My_Controller_IndexBase class in your library folder that both Content_IndexController and Page_IndexController inherit.
Did a little more research on the topic of the view script. You could change up the view's script paths during init() somewhere. I'm pretty sure this would probably need to be done in a ViewRenderer - but might also work inside the controller's init/action code.
$this->view->setScriptPath(
array(
realpath(APPLICATION_PATH+'/../path/to/other/module/views'),
) + $this->view->getScriptPath());
Script paths are processed Last In First Out according to the Zend_View_Abstract
For the second question:
If you don't want to write your own ViewRenderer, you can use $this->renderScript('parent/index.phtml') to render a specific view script. You could call that in your child controllers instead of letting the views be rendered for you automatically, or if your child controllers rely on the parent controller to do the rendering of the script you can just place that in your parent controllers.
I do that mode.
Frist I register a new name who a plugin in my index.php into the public folder:
/public/index.php
$autoloader = Zend_Loader_Autoloader::getInstance();
$autoloader->registerNamespace('modelo_');
Secound I create a new folder to put my controller
/library/modelo/
Third I create my controller model and put it into the folder created and rename it.
class Modelo_ModeloController extends Zend_Controller_Action
{
protected $_db = null;
protected $_menu = null;
protected $_util = null;
protected $_path = null;
... actions to my model
public function inicial(){}
}
and I extend this class im my application
class Sispromo_PedidoController extends Modelo_ModeloController
{
public function init(){}
....
}