I can't seem to store additional data in a separate contentpart attached to User. I have done the following:
Created a module
In the module I created a Model for ProfilePart and ProfilePartRecord
In the migration I created a table for ProfilePartRecord (from type ContentPartRecord)
In the migration I altered the typedefinition for User, by setting WithPart ProfilePart
I created a driver class, that has 2 edit methods, one for get and one for post (code snippets are below
I also created a handler that adds a storage filter for profilePartRepository of type ProfilePartRecord
Module Structure
Drivers/ProfilePartDriver.cs
Handlers/ProfileHandler.cs
Models/ProfilePart.cs
Models/ProfilePartRecord.cs
Views/EditorTemplates/Parts/profile.cshtml
Migrations.cs
Placement.info
Since I think the issue is in the Driver. This is my code:
Is it going wrong because the part is attached to User? Or am I missing something else.
public class ProfilePartDriver:ContentPartDriver
{
protected override string Prefix
{
get { return "Profile"; }
}
//GET
protected override DriverResult Editor(ProfilePart part, dynamic shapeHelper)
{
return ContentShape("Parts_Profile_Edit", () =>
shapeHelper.EditorTemplate(TemplateName: "Parts/Profile", Model: part, Prefix: Prefix));
}
//POST
protected override DriverResult Editor(ProfilePart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
I have used Skywalker's blog. There is one chapter about registering customers by using the User and adding your own content parts to it. Worked nice for me.
First of all - is your ProfilePart editor shown at all when you go to Dashboard and edit a given user? I noticed you're using Parts_Profile_Edit as a shape key, but actually use EditorTemplates/Parts/Profile.cshtml as a template. It's perfectly correct, but note that Placement.info file uses shape keys, so you have to use Parts_Profile_Edit as a shape name in there. Otherwise it won't get displayed.
Second - have you tried debugging to see if the second driver Editor method (the one for handling POST) is being called at all?
Like Bertrand suggested, I'd look into one of the existing modules that work (afaik there is one for user profile in the Gallery) and see the difference. It might be something small, eg. a typo.
Related
I have just discovered contextSuperClass and have been experimenting with using it to provide scope annotations when building a symbol table in a first pass (I have a forward reference DSL).
I set the option in the grammar:
options {
tokenVocab=MyLexer;
language = CSharp;
contextSuperClass = interpreter.MyParserRuleContext;
}
and I have a class that derives from ParserRuleContext:
public class MyParserRuleContext : ParserRuleContext
{
public MyParserRuleContext()
{ }
public MyParserRuleContext(ParserRuleContext parent, int invokingStateNumber) : base(parent, invokingStateNumber)
{
}
public IScope ContextScope { get; set; }
}
So far so good. I use ParseTreeWalker with a listener (Enter/Exit methods) to walk the tree for the 1st pass and build the symbol table adding local scopes, etc into my ContextScope custom property.
The first issue is of course after the symbol table pass I am at the end of the token stream - the tree is walked.
The 2nd parse uses a visitor to evaluate the final result.
I have two questions:
How do I "reset" the parser so that it is at the root again without loosing scopes I have added into my custom property?
The second question is broader, but similar. Is this even a reasonable way to add scope annotations to the parse tree?
I have previously tried to use ParseTreeProperty<IScope> to add scope annotations, but the problem is similar. During the 2nd phase, the context objects provided in the visitor are not the same objects added to ParseTreeProperty<IScope> concurrent dictionary from the 1st pass - so they are not found. Between the 1st & 2nd passes I have only found parser.reset() as a way to start the parser over, and (of course) it appears to fully reset everything and I loose the any state I created in the 1st pass.
I am likely missing completely missing something here - so any help to put me in the right direction will be greatly appreciated.
Here is the problem.
I've got a module called Contact. It lives in a directory called modules/Contact. I registered a namespace Contact for the module in app/Config/Autoload.php. Everything seems to load fine. Now I want to use a custom validation method to validate input coming from a form field. My question is about the best way to do it. My approach is as follows: According to CI4 documentation, Validation class loads App/Config/Validation.php when used for validation. In the file there is public property called $ruleSets, which points to an array, it contains names of classes with validation rules. I want to add my own class with validation methods to this array. According to CI4 documentation on validation, configuration classes, and their public properties in App/Config/ can be updated using registrars. So, I crated one such registrar, a class, in my Contact module. It lives in Contact\Config\Registrar.php . The class has public static method Validation, which returns an array with an updated $ruleSet. The new value is an array and contains all the standard validation class names + my validation class name. The trouble is public static method Validation I added in my registrar seems not to change the value of $ruleSet defined in App/Config/. Contact/Config/Registrar.php is loaded, but Validation method it contains is not called. So, what's the best method to add custom validation rules without having to updated files which live beyond my module? There is very little on registrars in CI4 documentation. Have I misunderstood how they work and how they are supposed to be used?
I had the same problem and my solution was to extend the validation class (better solutions are always welcome).
For example I wanted to write my own Auth module and use my own set of rules. My custom validation class lives under acme/Auth/Config/AuthValidation.php
<?php
namespace Acme\Auth\Config;
use Config\Validation;
class AuthValidation extends Validation
{
public $login = [
'username' => 'required',
'password' => 'required',
];
}
This way all validation rules in your main app are inherited.
And you would use this code in your controller to validate:
<?php
namespace Acme\Auth\Controllers;
use CodeIgniter\Controller;
use Acme\Auth\Config\AuthValidation as AuthValidation;
class Auth extends Controller
{
public function login()
{
//Instantiate the new AuthValidation class
$authValidationConfig = new AuthValidation();
//Since the validation class is a config for the validation service we set it for the validation service
$authValidation = \Config\Services::validation($authValidationConfig);
//Set the login rule from our AuthValidation class
$authValidation->setRuleGroup('login');
//Validate the request against our login rule
if($authValidation->withRequest($this->request)->run())
{
return 'Success';
}
else
{
return 'Failure';
}
}
}
It may be not the best solution because I'm pretty new to CodeIgniter but this is how I found my way around it.
Sidenote: The main problem seems to be that the class passed to the validation service constructor has to be an instance of "Config\Validation" so I had to extend it. But there might be other ways around it.
Feel free to correct me :)
I'm getting this error message, and any of the advice that I've seen does not appear to be applicable; i.e. all views, controllers and models are in the correct folders.
More detail:
I have a master view, which shows a graphical flowchart-like interface for interacting with the application. The user selects the "Open Study" symbol, and I redirect to another view which allows the user to select a Study to work with.
The OpenStudyController code retrieves the selected study and then redirects back to the master view:
public ActionResult SelectStudy( Guid? id )
{
// code elided for clarity
return RedirectToAction( "ActivateStudy", "Home" );
}
HomeController has a method called ActivateStudy(...), which does get invoked with the appropriate environment:
public ActionResult ActivateStudy()
{
// code elided for clarity
return View();
}
As I said, all views, controllers and models are in the correct folders.
When the "return View()" code in ActivateStudy() is executed, the error message occurs:
Server Error in '/' Application.
The view 'ActivateStudy' or its master was not found or no view engine supports the searched locations.The following locations were searched:
~/Views/Home/ActivateStudy.aspx
~/Views/Home/ActivateStudy.ascx
~/Views/Shared/ActivateStudy.aspx
~/Views/Shared/ActivateStudy.ascx
~/Views/Home/ActivateStudy.cshtml
~/Views/Home/ActivateStudy.vbhtml
~/Views/Shared/ActivateStudy.cshtml
~/Views/Shared/ActivateStudy.vbhtml
What am I missing? Some additional parameter in RedirectToAction(...)? Some new entry in RouteConfig?
If you have a _ViewStart.cshtml and it contains something like the following you do not need to specify the same layout in each view.
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
I believe your problem might be related to a missing slash. In your comment you said that your view contained the following line:
Layout = "~Views/Shared/_Layout.cshtml"
I think that needs to have a slash after the tilde.
Layout = "~/Views/Shared/_Layout.cshtml"
But, as I said, you should be able to remove that line entirely.
Well, I don't know if this is the "right" way to do it, but, in my HomeController.ActivateStudy() method, I return the Home "index" view:
public ActionResult ActivateStudy()
{
// code elided for clarity
return View( "Index" );
}
And this works.
It's at times like these where you realize you really don't know very much. Back to the books and code editor. Learn by doing.
As I exposed here, I would like to be able to query freely on the index without Orchard building the query for me.
I built a module, and inserted a copy of the SearchController, adding a new route...
To override default Orchard behavior concerning the query, I had to create new implementations of : ISearchService, IIndexManager, ISearchBuilder, IIndexProvider.
There are minor modifications from their default implementations, but they are needed.
That works as expected, but it currently override the default search too.
This is because I used the same interfaces and autofac takes my implementations.
I would like to be able to leave the default implementation untouched (at url /Search), and add my implementation at url (/LuceneSearch for example)
I suppose I must tell Autofac to use my implementations only for my controller by creating a class that inherits the autofac Module class.
Here is my problem : I don't know how to tell Autofac to use by default the Orchard Implementation, and just for my controller, use my implementation....
Another possibility is to create new interfaces, but it seems to me not really beautiful...
Can someone help me ? :)
Metadata feature will help you here. Also you have to register your implementations with PreserveExistingDefaults() modifier to preserve orchard's implementations by default.
Update:
Orchard registers all dependencies from Autofac's IModule implementation and from Orchard's IDependency one. All magic happen in Orchard's ShellContainerFactory class. Since ISearchService inherits from IDependency your implementation is registered by Orchard which overwrites default one.
You have two ways here:
Introduce your own empty interface IMySearchService which inherits from ISearchService. Implement and use it in your code where you need your implementation. Orchard will handle all registrations for your.
Orchard registers all IDependency implementations with "Feature" Metadata in ShellContainerFactory.RegisterType method. You can read this metadata in your code and choose your implementation (see link to wiki above). Feature class contains all necessary information about your module.
Hope this will help you.
A simpler method without dabbling with the intricacies of Autofac would be to use an IEnumerable<IInterface> variable in your controller/driver at url "/LuceneSearch" to hold all implementations of IInterface and choose which to consume.
For example, in order to consume your implementation of IIndexManager, you would put the following in your controller or driver
public class MyCustomPartDriver : ContentPartDriver<MyCustomPart> {
private readonly IEnumerable<IIndexManager> _indexManagers;
public MyCustomPartDriver(IEnumerable<IIndexManager> indexManagers) {
_indexManagers = indexManager;
}
protected override DriverResult Display(MyCustomPart part, string displayType, dynamic shapeHelper) {
//Use your implementation of IIndexManager
var indexManager = _indexManagers.First(im => im is MyCustomIndexManager);
//Get the ISearchBuilder from your implementation of IIndexManager
var searchBuilder = indexManager.HasIndexProvider() ? indexManager.GetSearchIndexProvider().CreateSearchBuilder("Search") : new NullSearchBuilder();
//perform search on the indexing engine
var contentItemIds = searchBuilder.
.WithField("type", "MyCustomType").Mandatory().ExactMatch()
.Parse("mycustompart-keyword", part.Keyword).Mandatory()
.Search()
.Select(h => h.ContentItemId)
.ToList();
//do stuff with the results returned and return a DriverResult using the ContentShape method. Well, you know the drill.
}
}
If you don't want to have autofac resolve your own implementation by default, then don't implement the public interface.
I need to perform a search on several entities with the same string then order the results.
I've heard/read a little about FOSElasticaBundle, would this bundle be able to do it? It seems (to me) to have almost to much features for this purpose and I'm not sure it could run on a shared server (hostgator).
The other solution I can think of at the moment is doing the search "manually" (by using join and union) but I'm wondering where should I put such a function: in an existing controller, a new one, a new bundle or somewhere else?
I'm worried as well that this manual solution could come to a cost, especially on some non-indexable fields.
You would do custom entity repositories. Check out the docs. Basically this extends the default FindAll, FindOneBy, etc.
You would have a function like so:
class MyEntityRepository extends Doctrine\ORM\EntityRepository {
public function findByCustomRule(){
//this is mapped to your entity (automatically adds the select)
$queryBuilder = $this->createQueryBuilder('someAlias');
$queryBuilder->orderBy('...');
//this is mapped to any entity
$queryBuilder = $this->getEntityManager()->createQueryBuilder();
$queryBuilder->select('...');
//result
$result = $queryBuilder->getQuery()->getResult();
}
}
This class is defined in the doctrine mapping and lives inside the Entity folder.. Check the docs out and you should get a basic idea.