Insert link in String field if substring matches - grails

I have a "comments" String field in my domain. On each save or update of the field I want to check if the field contains a sub string that matches a String from another field of the same domain (or different domain for that matter). I need to run through all the instances of that field to see if there is a match. If it matches I want to transform it into a link inside the comments field to a show action for that entry matching the sub string.
So for example, a comments field for a product with a serial number would note if the product has been replaced by another one by giving it's serial number in the comments field. Like: "This product was replaced by SN1234". I want to automatically transform SN1234 into a link to show the product with serial number SN1234.
What is the best way to go about this ? In the controller, in the GSP ? How ?

As long as the column you're trying to match on is indexed, you'll just need to do a query for the match and if found, modify your comment to include the URL. Controller or Service doesn't really matter for the lookup (although I would probably put it in a service). You'll want to be sure the search is not transactional so it will be as fast as it can be. No way I would do any of this in a GSP.
To insert the link, you could a simple find and replace. Once you know which text you want turned into the link, pseudo code follows:
def comment = "This product was replaced by SN1234"
def match = "SN1234"
def link = g.link(action: "show", controller: "product", id: "${product.id}", match)
comment = comment.replace(match, link)
Which you would then end up with
"This product was replaced by <a href='/product/1234'>SN1234</a>"
There may be more efficient ways to do this, but this is a good place to start.

You can use GORM events to do it in your domain. So whenever the domain is inserted/updated you can check that your field has been changed. Then you can insert your link.
def beforeInsert() {
yourMethod()
}
def beforeUpdate() {
if (isDirty('yourField')) {
yourMethod()
}
}

Related

connect two gsp screens to three domains in grails

so I am working on a web app when the users click on create on the list screen, it takes them to a page where they have to enter some information and then they click on "next" and it will take them to another gsp page where they have to enter data for two domains but none of the data are stored yet in the tables but when they click on "create" button, everything get stored in the database
I was looking for examples but couldnt find any.
I know how to call the record and edit it since all the domains or the tables share id number so I can use it to retrive data. but my problem is when I transfer from the first gsp screen to another I want to save the instance and then when the users click on create, data goes to the three tables
any idea how to do that? I am still beginner and trying to learn
thank you
It sounds kind confusing what you wanna do and I am not sure I completely understood. Because you want to change the whole page, and yet, not to lose those previous answers, right?. Is there a specific reason why you want to do this?
I believe you could sent all the information from the first gsp to a controller and save everything in the "session" (e.g session.name = params.name, session.age = params.age), redirect/render the other gsp and later on, you get the info back from the session plus the info that just came and save everything. This is probably not a very good solution, but this is the only way I figured this out.
:)
Just an idea, I haven't used it until now, but I will some day in the future to modify the dialog-flow in my current application...:
Wouldn't that be a good example for the webflow-plugin?
This is what command objects are used for. Any time you have a collection of data that you want to collect in a form and then "generate" multiple domain objects from, then this is the way.
The idea is to create a single class that has all the information that you want to collect on the form. You post the filled form data back to the controller save action which then validates that the data is complete using the command object constraints. Once you are happy with everything, you then use the data in the command object to create/update your domain objects.
We consider the use of command objects a Grails best practice. You can provide custom validation support in the command object that looks for and validates relationships in the data that are difficult to do with the domain objects. We often write factory methods in the command class that produce a new or updated domain object, making it very convenient for unit testing.
See the "Command Objects" section of the "Web Layer" in the manual for details. In Grails 2, command objects are classes with the #Validateable annotation and in Grails 3 they implement the Validateable trait. If you declare your command class as an inner class in the controller, then it is automatically validateable. We've found that we prefer to declare them in src/groovy rather than as inner classes because they are easier for someone unfamiliar with the code to find.
So amongst all of these answers you have your answer but in all honesty I think it is well beyond your own comprehension at this point.
Assuming you have this example as a form
http://code.runnable.com/UevQr3zfd_oaAAGn/jquery-ui-tabs
On tab 1 you have
Name
Age
On tab 2 you have
Address
Postcode
Then you have two domain class
Class User {
String name
String age
Address address
}
Class Address {
String address1
String postcode
}
So a user has name age and also binded to address, whilst address has address1 and postcode
now your controller action
def save(MyBean bean) {
Address address = new Address(bean.loadAddress()).save()
User user = new User()
def userMap = bean.loadUser()
user.age=userMap.age
user.name=userMap.name
//The above object that got saved first
user.addresss=address
user.save()
render "hopefully this should have saved it as expected"
}
In src/main/groovy/yourPackage/MyBean.groovy
package yourPackage
import grails.validation.Validateable
Class MyBean implements Validateable{
String name
String age
Address address
String address1
String postcode
//Now declare your constraints like your domainClass add validator where required to add additional verification/validation to your objects sent back
//This will return all the objects required as a map to save address domain class
protected Map loadAddress() {
Map results=[:]
results.with {
address1=address1
postcode=postcode
}
return results
}
//this will return the user object
protected Map loadUser() {
Map results=[:]
results.with {
name=name
age=age
}
}
}
Other fairly complex validation bean examples:
PhotosBean CustomerChatBean ScheduleBaseBean
Other points of reference:
As I say I think as a beginner this may take you a while to get your head around but hoping with what is provided it will become a lot clearer now
E2A
It is really complicated!! can I have two gsp screens instead of jquery tabs
That doesn't make much sense.
You can have two actions which one just passes params onto 2nd gsp ?
So
def TestController {
def index() {
render view: page1
}
//where page 1 is the first form and submits to action2
//action2 picks up parmas from page1
def action2() {
render view: page2, model:[params:params]
}
}
in page2.gsp you have
<g:form action="action3">
<g:hiddenField name="originalName" value="${params.originalValue}"/>
<g:hiddenField name="originalName2" value="${params.originalValue2}"/>
<g:hiddenField name="originalName3" value="${params.originalValue3}"/>
Then your actual form content
The problem with doing this this way, is does action2 need to verify params received from page1 ? if so it needs to either render original page or page2 depending.
Once submitted to page2 the hiddenFields can be tampered with by end user so what was validated may be invalid now. You will need some form of a way of revalidating all those again.
Using validation methods above you could just call the validate() functions or maybe build some md5 check of initial values vs what is now sent from page2.
Either way if you don't care about validation and just want to see it work then above is the simplest way.
can I have two gsp screens instead of jquery tabs
in page1 you can just do <g:include action="page2"> and include a 2nd gsp within first but in all honesty page 1 could have just contained both actions in 1 page. which is why it don't make sense

Rails modeling admin-controlled fields

I have 10 drop-down fields in a "post" form that I want to be controlled by the admin user.
Currently, I have another model called "post fields"
class PostField
include Mongoid::Document
field :family, type: String
field :project, type: String
field :event, type: String
field :testmode, type: String
field :location, type: String
end
I then use these documents to populate each dropdown for a new post.
I have 2 dilemmas:
If the admin adds a PostField document for one dropdown, it creates a whole document. Not the most efficient thing, but I can deal with that.
If the admin changes the name of a field, all associated posts won't match.
This is effectively a tagging model, but poorly implemented b/c I'm not sure how to do it.
ie, I want to have a controlled list of locations: ["Upstairs", "Downstairs", "Bathroom"] so users can't just use a text input and say "Restroom" instead. But if an admin wants to change "Bathroom" to "Restroom", I want the posts to update accordingly.
Hope this isn't too complicated.
So you want each PostField to be of a certain type, or location? So you want to limit the values that go into :location?
What you have to do is create another model and table called locations, and that will contain a list of locations like Upstairs, Downstairs, Bathroom, etc. It has and id, and a name. In your PostField, you will join to the Locations with has_one :location, and the database table will have location_id. Then the admin can change Location.name to anything they want, while all the PostFields will show the updated label. In the view, use postField.location.name to display the label for that field.
Not sure to anderstand what you want to do.
With this schema, you should be able to create a "post" with static fields and dynamic fields.
PostField
The "sorting" field is used to sort post's field with respect to each other.
The "code" field will contain the code to identify post's field. Once created, it should be static.
If you want to make it dynamic, you should add a field "label" so administrator can add new post's field
PostFieldValue
The "sorting" field is used to sort values with respect to each other.
The "label" field is in your example ["Upstairs", "Downstairs", "Bathroom"]
In model
You should create a method to get the value based on post's field.
Improvements
You can improve the schema by adding a "isdefault" field to values so administrator can force the default value in drop-down.
You can categorize post's field if needed

How to create multiple domain objects from a GSP page

I have a Person class with two properties: name and address. I want to build a GSP page which allows for 10 users to be created at one time. This is how I'm implementing it and was wondering if there is a better way:
First, make 20 text boxes in the GSP page - 10 with someperson.name and 10 with someperson.address field names (make these in a loop or code them all individually, doesn't matter).
Second, process the submitted data in the controller. The someperson object has the submitted data, but in a not-so-nice structure ([name: ['Bob', 'John'], address: ['Address 1', 'Address 2']]), so I call transpose() on this to be able to access name, address pairs.
Then, build a list of Person objects using the pairs obtained from the previous step and validate/save them.
Finally, if validation fails (name cannot be null) then do something... don't know what yet! I'm thinking of passing the collection of Person objects to the GSP where they are iterated using a loop and if hasErrors then show them... Don't know how to highlight the fields which failed validation...
So, is there a better way (I should probably ask WHAT IS the better way)?
You should use Grails' data-binding support by declaring a command object like this
class PersonCommand {
List<Person> people = []
}
If you construct your form so that the request parameters are named like this:
person[0].name=bob
person[0].address=england
person[1].name=john
person[1].address=ireland
The data will be automatically bound to the personCommand argument of this controller action
class MyController {
def savePeople = {PersonCommand personCommand->
}
}
If you call personCommand.validate() it might in turn call validate() on each Person in people (I'm not sure). If it doesn't you can do this yourself by calling
boolean allPersonsValid = personCommand.people.every {it.validate()}
At this point you'll know whether all Person instances are valid. If they are not, you should pass the PersonCommand back to the GSP and you can use the Grails tags:
<g:eachError>
<g:hasErrors>
<g:renderErrors>
to highlight the fields in errors. If you're not exactly sure how to use these tags to do the highlight, I suggest you run grails generate-all for a domain class and look at the GSP code it generates.

Apostrophe CMS: Engine Creation

I'm attempting to create a Product Engine for Apostrophe. I'm having trouble extending the Page Settings form, at the moment I want to add a simple textarea to add a synopsis to the page - eventually I want to add Product settings but I need to get the basics working first.
I've created a form and a settings partial, it's displaying fine and saving the data (with the help of a little hack - might not be correct). The trouble I'm having is when you edit a page the data is not being pulled back in to the form. To be honest I probably doing something fundamentally wrong but I lack experience in Symfony.
My table schema
ccProduct:
tableName: cc_product
actAs:
Timestampable: ~
columns:
page_id:
type: integer
notnull: true
synopsis:
type: text
relations:
Page:
class: aPage
local: page_id
foreign: id
type: one
onDelete: CASCADE
My form ccProductEngineForm.class.php
class ccProductEngineForm extends ccProductForm
{
public function __construct($object = null, $options = array(), $CSRFSecret = null)
{
// when editing the page the values are not show, this is an attempt to get it to work - it still doesn't :(
$page_id = sfContext::getInstance()->getRequest()->getParameter('id');
sfContext::getInstance()->getRequest()->setParameter('page_id', $page_id);
$ccProduct = Doctrine::getTable('ccProduct')->findOneByPageId($page_id);
if ($ccProduct) {
sfContext::getInstance()->getRequest()->setParameter('id', $ccProduct->getId());
}
// aPageForm object is passed in
parent::__construct(null, $options, $CSRFSecret); // construct normally
//$this->mergeForm(new aPageForm($object, $options, $CSRFSecret)); // merge the aPageForm - Nope, ignore it!?
}
public function setup() {
parent::setup();
$this->useFields(array('synopsis'));
$this->widgetSchema->setNameFormat('enginesettings[%s]');
$this->widgetSchema->setFormFormatterName('aPageSettings');
}
protected function doSave($con = null)
{
// page_id is missing! possible bug? BaseaActions.class.php ~ 520
$this->values['page_id'] = sfContext::getInstance()->getRequest()->getParameter('enginesettings[pageid]');
parent::doSave($con);
}
}
Thanks in advance for any help
EDIT:
Thanks for your answer Tom, I'll try to add a little more detail.
I was aware that a page object is passed into the Engine, but I wasn't exactly sure what do to with it - see my confused line of code:
//$this->mergeForm(new aPageForm($object, $options, $CSRFSecret)); // merge the aPageForm - Nope, ignore it!?
To clarify my 'product' is a page that uses the ccProduct engine. I now want to add extra information to that page. Does that make sense? In your words..
Are you trying to actually create a unique product that has its sole "home" on a product engine page? That's what subclassing ccProductForm would do
Yes :)
EDIT 2:
Following Tom's first suggestion (Apostrophe CMS: Engine Creation) I was able to extend the aPage table with my extra fields and the Engine is now saving these.
However, the standard aPageTable::getPagesInfo function isn't returning the fields I saved. I assume I'll have to select these separately?
EDIT 3:
aPageTable::retrieveBySlug() will do the job :)
REVISITED
I decided to revisit this and try Tom's second approach..
The other approach (if for whatever reason you don't want extra columns in aPage) is to keep your ccProduct table and fetch the relevant one
I managed to get this working, my ccProductEngine form constructor now looks like this..
class ccProductEngineForm extends ccProductForm
{
public function __construct($aPage = null, $options = array(), $CSRFSecret = null)
{
$page_id = $aPage->getId();
if ($page_id) {
$product = Doctrine_Core::getTable('ccProduct')->findOneByPage_id($page_id);
if ($product) {
$ccProduct = $product;
} else {
$ccProduct = new ccProduct();
}
}
parent::__construct($ccProduct, $options, $CSRFSecret);
}
I hope this helps someone :)
The main thing to remember is that your engine settings form receives a page object as the first parameter to the constructor, and you need to associate whatever your data is with that page object. Usually the engine settings form is a subclass of aPageForm, but it does not have to be. All that is required is that you associate your product object(s) with the page object in some way. Depending on your goals you probably want a refClass that creates a one-to-many relationship between product engine pages and products, and a form for manipulating those relationships.
From your code it is difficult for me to guess what you really want to do. Are you trying to actually create a unique product that has its sole "home" on a product engine page? That's what subclassing ccProductForm would do. Or do you just want to select an existing product from the product table and associate it with each engine page? Or do you want to select one or more products and associate them with the engine page?
Stuffing things into the request object is definitely not the way to go (:
Please clarify and I can help you further.
Tom Boutell, Apostrophe senior developer
There are two approaches you could follow here.
One is to just extend the schema of aPage in your project level config/doctrine/schema.yml file:
aPage:
columns:
synopsis:
type: text
Now every aPage object has a synopsis column, which will be null by default, and your engine settings form can just manipulate that one column. Your engine form subclasses aPageForm. You don't need a constructor at all (the default one will suit you), and your configure() method is just:
$this->useFields(array('synopsis'));
Boom, you have a textarea for the synopsis that appears when the page type is set to this engine. You don't need a ccProduct table at all.
The other approach (if for whatever reason you don't want extra columns in aPage) is to keep your ccProduct table and fetch the relevant one. Your engine form class then does not subclass aPageForm, and your constructor has to use the page passed to it to fetch the related ccProduct object (using a Doctrine relation) or create a new one if there is none yet. This is not difficult, but so far it looks like you can keep it even simpler by just adding a column to aPage.

extbase mapping to an existing table doesn't work

I've extended the pages table and now I want to use some of the data in a domain object called "Tags".
So I tried the following in the /Configuration/TypoScript/setup.txt:
plugin.myextension.persistence.classes.Tx_myextension_Domain_Model_Tag {
mapping {
tableName = pages
recordType = Tx_myextension_Domain_Model_Tag
columns {
tx_myextension_tag_name.mapOnProperty = name
uid.mapOnProperty = id
}
}
}
But It seems that the extension tries to access the table Tx_myextension_Domain_Model_Tag (which doesn't exist)
This is the error I receive:
Tx_Extbase_Persistence_Storage_Exception_SqlError`
Table 'tx_myextension_domain_model_tag' doesn't exist: SELECT tx_myextension_domain_model_tag.* FROM tx_myextension_domain_model_tag WHERE tx_myextension_domain_model_tag.id = '24' LIMIT 1
What have I done wrong?
Don't forget to include your extension typoscript template into your template ( template > edit whole template > include static templates ), otherwise your setup.txt is not evaluated.
To check which recordType(s) are acceptable use Configuration module in BE, in $TCA section find your table ([pages] in this case) and check type column (...[ctrl][type] - for pages it's 'doktype', which decides if page record is standard page or sysfolder etc.).
This column is tinyint(3) in database, so you can not write value 'Tx_myextension_Domain_Model_Tag' to it. Create in your ext new doktype identified by number and set recordType to it.
Optionaly you can just remove recordType from mapping config if page's type doesn't matter to you.
did you try "config.tx_extbase" instead of "plugin.myextension"?
Something like
config.tx_extbase.persistence.classes.Tx_MyExtension_Domain_Model_Tag.mapping.tableName = pages
works for me.
Tx_myextension_Domain_Model_Tag is the name of your object right ? But I guess this is not the name of the table you are trying to access. So my guess is that the name you are providing into the value "tableName" is wrong. What does "pages" contain ?
Have you specified the individual pages as recordtype Tx_myextension_Domain_Model_Tag ?
It's supposed to go into the doctype field of the pages table (therefore you'll need to change the mysql datatype of that field.
Otherwise Extbase doesn't know that this specific page is an extbase record and not a regular page.
See more about single table inheritance (STI) in Extbase: http://pascal-jungblut.com/blog/blog-post/2010/11/06/single-table-inheritance-in-extbase.html
do you do this in a typo3 call with eID?
here some configuration is not loaded ..
if yes, try if loading all configuration solves the problem:
...
$GLOBALS['TSFE'] = \t3lib_div::makeInstance('tslib_fe', $TYPO3_CONF_VARS, $_GET["id"], 0, true);
//$GLOBALS['TSFE'] = new $temp_TSFEclassName();
$GLOBALS['TSFE']->connectToDB();
$GLOBALS['TSFE']->initFEuser();
$GLOBALS['TSFE']->determineId();
$GLOBALS['TSFE']->getCompressedTCarray();
$GLOBALS['TSFE']->initTemplate();
$GLOBALS['TSFE']->getConfigArray();
...
Take care of the proper naming convention regarding FE-Plugins:
plugin.tx_myextension

Resources