Jump to content
Gadgetto

Custom multi-column Fieldtype + Inputfield - extending for multi language usage?

Recommended Posts

Hi,

for my GroupMailer module I've created a custom Fieldtype + Inputfield module which provides multi-column field values. The first field column is a visible text field and there are some other columns which are not presented to user (they are rendered as hidden form fields).

262017940_Bildschirmfoto2019-02-20um19_42_04.png.0d61e10a7761a26218c16bc01c60aea4.png

This is the database schema:

$schema['data'] = 'text NOT NULL'; // we're using 'data' to represent our 'subject' field
$schema['sendstatus'] = 'tinyint NOT NULL DEFAULT 0'; // message send status
$schema['recipients'] = "int(10) unsigned NOT NULL DEFAULT 0";  // recipients counter
$schema['sent'] = "int(10) unsigned NOT NULL DEFAULT 0";  // sent counter
$schema['started'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending start
$schema['finished'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending finished

This are the ___wakeupValue and ___sleepValue methods:

Spoiler

    /**
     * Convert from DB storage to API value.
     *
     * @param Page $page
     * @param Field $field
     * @param string|int|array $value
     * @return string|int|array|object $messagemeta
     *
     */
    public function ___wakeupValue(Page $page, Field $field, $value) {

        // if for some reason we already get a valid value, then just return it
        if ($value instanceof MessageMeta) return $value; 

        // start a blank value to be populated
        $messagemeta = $this->getBlankValue($page, $field); 

        // if we were given a blank value, then we've got nothing to do: just return a blank MessageMeta
        if (empty($value) || !is_array($value)) return $messagemeta; 

        // create new MessageMeta object
        $messagemeta = new MessageMeta();
        $messagemeta->subject = $value['data']; // @note: we're converting 'data' to 'subject'
        $messagemeta->sendstatus = $value['sendstatus'];
        $messagemeta->recipients = $value['recipients'];
        $messagemeta->sent = $value['sent'];
        $messagemeta->started = $value['started'];
        $messagemeta->finished = $value['finished']; 

        return $messagemeta;  
    }

    /**
     * Convert from API to DB storage value. 
     *              
     * @param Page $page
     * @param Field $field
     * @param string|int|array|object $value
     * @return array
     *
     */
    public function ___sleepValue(Page $page, Field $field, $value) {
        $messagemeta = $value;
        $sleepValue = array();
        // if we are given something other than an MessageMeta, 
        // then just return a blank array
        if (!$messagemeta instanceof MessageMeta) return $sleepValue; 

        // set MessageMeta to sleepValue
        $sleepValue = array(
            'data' => $messagemeta->subject,  // @note: subject is becoming data
            'sendstatus' => (int) $messagemeta->sendstatus,
            'recipients' => (int) $messagemeta->recipients, 
            'sent' => (int) $messagemeta->sent, 
            'started' => (int) $messagemeta->started, 
            'finished' => (int) $messagemeta->finished, 
        );

        return $sleepValue;
    }

 

Now I try to extend this Fieldtype/Inputfield to provide multi language features.

Only the first value ("data" which represents the "subject" field) should be/needs to be multi language!

I had a look at the built in Fieldtypes (e.g FieldtypeText & FieldtypeTextLanguage) which provides multi language support but I couldn't find a similar case (multi-value field with language support). All built in Fieldtypes are single-value fields.

I know this is a very "general" question but maybe somebody could push me in the right direction?

Share this post


Link to post
Share on other sites

I've never created any custom field types myself, but maybe this recent thread helps:

 

Share this post


Link to post
Share on other sites
On 2/20/2019 at 7:58 PM, dragan said:

I've never created any custom field types myself, but maybe this recent thread helps:

Thanks, but this doesn't help. It addresses a different problem.

Share this post


Link to post
Share on other sites

I' really stuck here guys... 😞 Documentation is no help. Comparing with other multi-language fields doesn't help because they are all single-value fields.

Here are the full sources of my multi-value input field:

FieldtypeMessageMeta.module.php

Spoiler

<?php namespace ProcessWire;

/**
 * Fieldtype that stores MessageMeta for GroupMailer Messages
 *
 */

class FieldtypeMessageMeta extends Fieldtype {

    public static function getModuleInfo() {
        return array(
            'title' => 'GroupMailer MessageMeta',
            'version' => 1,
            'summary' => 'Fieldtype that stores meta data for GroupMailer Messages.',
            'installs' => 'InputfieldMessageMeta',
            //'requires' => 'ProcessGroupMailer', // needed to be uninstalled with ProcessGroupMailer!
        );
    }

    /**
     * Initialize this Fieldtype
     *
     */
    public function init() {
        parent::init();
        require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'MessageMeta.php'; 
    }

    /**
     * Return the required Inputfield used to populate a field of this type
     * 
     * @param Page $page
     * @param Field $field
     * @return null|_Module|\Inputfield|\Module
     * @throws WirePermissionException
     * 
     */
    public function getInputfield(Page $page, Field $field) {
        /** @var InputfieldMessageMeta $inputfield */
        $inputfield = $this->modules->get('InputfieldMessageMeta'); 

        // our inputfield requires a Page and Field
        $inputfield->setPage($page);
        $inputfield->setField($field); 

        return $inputfield; 
    }

    /**
     * Return all Fieldtypes derived from FieldtypeMessageMeta, which we will consider compatible
     *
     */
     public function ___getCompatibleFieldtypes(Field $field) {
        $fieldtypes = $this->wire(new Fieldtypes());
        foreach ($this->wire('fieldtypes') as $fieldtype) {
            if ($fieldtype instanceof FieldtypeMessageMeta) {
                $fieldtypes->add($fieldtype);
            }
        }
        return $fieldtypes; 
    }

    /**
     * Return a blank ready-to-populate version of a field of this type
     *
     */
    public function getBlankValue(Page $page, Field $field) {
        $messagemeta = new MessageMeta($page);
        $messagemeta->setTrackChanges(true); 
        return $messagemeta; 
    }


    /**
     * Convert from DB storage to API value.
     *
     * @param Page $page
     * @param Field $field
     * @param string|int|array $value
     * @return string|int|array|object $messagemeta
     *
     */
    public function ___wakeupValue(Page $page, Field $field, $value) {

        // if for some reason we already get a valid value, then just return it
        if ($value instanceof MessageMeta) return $value; 

        // generate a blank value to be populated
        $messagemeta = $this->getBlankValue($page, $field); 

        // if we were given a blank value, then we've got nothing to do: just return a blank MessageMeta
        if (empty($value) || !is_array($value)) return $messagemeta; 

        // create new MessageMeta object
        $messagemeta = new MessageMeta();
        $messagemeta->subject = $value['data']; // @note: we're converting 'data' to 'subject'
        $messagemeta->sendstatus = $value['sendstatus'];
        $messagemeta->recipients = $value['recipients'];
        $messagemeta->sent = $value['sent'];
        $messagemeta->started = $value['started'];
        $messagemeta->finished = $value['finished']; 

        return $messagemeta;  
    }

    /**
     * Convert from API to DB storage value. 
     *              
     * @param Page $page
     * @param Field $field
     * @param string|int|array|object $value
     * @return array
     *
     */
    public function ___sleepValue(Page $page, Field $field, $value) {
        $messagemeta = $value;
        $sleepValue = array();
        // if we are given something other than an MessageMeta, 
        // then just return a blank array
        if (!$messagemeta instanceof MessageMeta) return $sleepValue; 

        // set MessageMeta to sleepValue
        $sleepValue = array(
            'data' => $messagemeta->subject,  // @note: subject is becoming data
            'sendstatus' => (int) $messagemeta->sendstatus,
            'recipients' => (int) $messagemeta->recipients, 
            'sent' => (int) $messagemeta->sent, 
            'started' => (int) $messagemeta->started, 
            'finished' => (int) $messagemeta->finished, 
        );

        return $sleepValue;
    }

    /**
     * Given a value, make it clean for storage within a Page
     *
     * @param Page $page
     * @param Field $field
     * @param int|object|WireArray|string $value
     * @return int|null|object|MessageMeta|WireArray|string
     * @throws WireException
     * 
     */
    public function sanitizeValue(Page $page, Field $field, $value) {

        // if given a blank value, return a valid blank value
        if (empty($value)) return $this->getBlankValue($page, $field, $value); 

        // if given something other than an MessageMeta, throw an error
        if (!$value instanceof MessageMeta) {
            throw new WireException("Value set to field '$field->name' must be MessageMeta data"); 
        }

        // note that sanitization of individual fields within a given MessageMeta is already 
        // performed by the MessageMeta::set() method, so we don't need to do anything else here.

        return $value; 	
    }

    /**
     * Format a value for output, called when a Page's outputFormatting is on
     *
     */
    public function formatValue(Page $page, Field $field, $value) {
        // we actually don't need to do anything in here since each MessageMeta object
        // is doing this work in the MessageMeta::get() method.
        return $value; 
    }

    /**
     * Return the database schema that defines a MessageMeta object
     *
     */
    public function getDatabaseSchema(Field $field) {
        $schema = parent::getDatabaseSchema($field); 

        $schema['data'] = 'text NOT NULL'; // we're using 'data' to represent our 'subject' field
        $schema['sendstatus'] = 'tinyint NOT NULL DEFAULT 0'; // message send status
        $schema['recipients'] = "int(10) unsigned NOT NULL DEFAULT 0";  // recipients counter
        $schema['sent'] = "int(10) unsigned NOT NULL DEFAULT 0";  // sent counter
        $schema['started'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending start
        $schema['finished'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending finished

        // indexes, for any fields that need to be searchable from selectors
        $schema['keys']['data'] = 'FULLTEXT KEY `data` (`data`)';
        $schema['keys']['sendstatus'] = 'KEY sendstatus (sendstatus)';
        $schema['keys']['recipients'] = "KEY recipients (recipients)";
        $schema['keys']['sent'] = "KEY sent (sent)";
        $schema['keys']['started'] = "KEY started (started)";
        $schema['keys']['finished'] = "KEY finished (finished)";

        return $schema; 
    }

    /**
     * Method called when the field is database-queried from a selector 
     *
     */
    public function getMatchQuery($query, $table, $subfield, $operator, $value) {

        // If searching 'subject' then assume our default (data) field 
        if ($subfield == 'subject') $subfield = 'data';

        // if value is a formatted date, convert it to unix timestamp
        if (!ctype_digit("$value")) $value = strtotime($value);

        return parent::getMatchQuery($query, $table, $subfield, $operator, $value); 
    }

}

 

FieldtypeMessageMetaLanguage.module.php

Spoiler

<?php namespace ProcessWire;

require_once wire('config')->paths->modules . 'LanguageSupport/FieldtypeLanguageInterface.php';

/**
 * Multi-language capable MessageMeta field
 *
 */

class FieldtypeMessageMetaLanguage extends FieldtypeMessageMeta implements FieldtypeLanguageInterface {

    public static function getModuleInfo() {
        return array(
            'title' => 'GroupMailer MessageMeta (Multi-language)',
            'version' => 1,
            'summary' => 'Fieldtype that stores meta data for GroupMailer Messages in multiple languages.',
            'requires' => 'LanguageSupportFields',
            //'requires' => 'ProcessGroupMailer', // needed to be uninstalled with ProcessGroupMailer!
        );
    }

    /**
     * Sanitize value for storage
     * 
     * @param Page $page
     * @param Field $field
     * @param LanguagesValueInterface|string $value
     * @return LanguagesPageFieldValue
     *
     */
    public function sanitizeValue(Page $page, Field $field, $value) {
        if (is_object($value) && $value instanceof LanguagesPageFieldValue) {
            // great, already what we wanted
        } else {
            // convert it to a LanguagesPageFieldValue
            $pageValue = $page->data($field->name); // raw unformatted value, with no load possible
            if (!$pageValue instanceof LanguagesPageFieldValue) {
                $pageValue = new LanguagesPageFieldValue($page, $field, $pageValue); // #98
            }
            if (is_array($value)) {
                $pageValue->importArray($value); 
            } else {
                $user = $this->wire('user');
                $language = $user ? $user->language : null;
                if ($language) $pageValue->setLanguageValue($language->id, (string) $value); 
            }
            $value = $pageValue; 
        }
        return $value; 
    }

    /**
     * Return the database schema in specified format
     * 
     * @param Field $field
     * @return array
     *
     */
    public function getDatabaseSchema(Field $field) {
        $schema = parent::getDatabaseSchema($field);
        
        $languageSupport = $this->wire('modules')->get('LanguageSupport'); 
        $maxIndex = (int) $this->wire('database')->getMaxIndexLength();
    
        // note that we use otherLanguagePageIDs rather than wire('languages') because
        // it's possible that this method may be called before the languages are known 
        foreach ($languageSupport->otherLanguagePageIDs as $languageID) {
            // $schema['data' . $languageID] = $schema['data'];
            $schema['data' . $languageID] = 'text';
            $schema['keys']["data_exact{$languageID}"] = "KEY `data_exact{$languageID}` (`data{$languageID}`($maxIndex))";
            $schema['keys']["data{$languageID}"] = "FULLTEXT KEY `data{$languageID}` (`data{$languageID}`)";
        }
    
        return $schema;
    }

    /**
     * Format value for output, basically typecasting to a string and sending to textformatters from FieldtypeText
     * 
     * @param Page $page
     * @param Field $field
     * @param LanguagesValueInterface|string $value
     * @return string
     *
     */
    public function formatValue(Page $page, Field $field, $value) {
        return parent::formatValue($page, $field, (string) $value); 
    }

    /**
     * Given a value, return an portable version of it as array
     *
     * @param Page $page
     * @param Field $field
     * @param string|int|float|array|object|null $value
     * @param array $options Optional settings to shape the exported value, if needed.
     * @return string|float|int|array
     *
     */
    public function ___exportValue(Page $page, Field $field, $value, array $options = array()) {
        if (isset($options['sleepValue'])) {
            // allow a sleepValue option, for use by other language Fieldtypes that delegate
            // their exportValue to this one, like FieldtypeTextareaLanguage
            $sleepValue = $options['sleepValue'];
        } else {
            $sleepValue = $this->sleepValue($page, $field, $value);
        }
        $exportValue = array();
        foreach ($sleepValue as $k => $v) {
            if ($k === 'data') {
                $exportValue['default'] = $v;
            } elseif (strpos($k, 'data') === 0) {
                $languageID = substr($k, 4);
                $language = $this->wire('languages')->get((int) $languageID);
                $exportValue[$language->name] = $v;
            } else {
                $exportValue[$k] = $v;
            }
        }
        return $exportValue;
    }
    
    public function ___importValue(Page $page, Field $field, $value, array $options = array()) {
        if (is_null($value)) $value = '';
        if (is_string($value)) {
            $v = $value;
            $value = $field->type->exportValue($page, $field, $page->getUnformatted($field->name), $options);
            $value['default'] = $v; 
        }
        if (!is_array($value)) {
            throw new WireException('Array value expected for multi-language importValue');
        }
        /** @var Languages $languages */
        $languages = $this->wire('languages');
        /** @var LanguagesPageFieldValue $importValue */
        $importValue = $page->get($field->name); 
        foreach ($value as $languageName => $languageValue) {
            $language = $languages->get($languageName); 
            if (!$language->id) continue; 
            $importValue->setLanguageValue($language->id, $languageValue); 
        }
        return $importValue;
    }

}

 

MessageMeta.php

Spoiler

<?php namespace ProcessWire;

/**
 * An individual MessageMeta object for a Page
 *
 */
class MessageMeta extends WireData {

	/** @var string $dateFormat Holds the date format from wire config */
	protected $dateFormat;

	/**
	 * We keep a copy of the $page that owns this MessageMeta so that we can follow
	 * its outputFormatting state and change our output per that state
	 *
	 */
	protected $page; 

	/**
	 * Construct a new MessageMeta object
	 *
	 */
	public function __construct() {
		$this->dateFormat = $this->wire('config')->dateFormat;

		// define the fields that represent our MessageMeta object (and their default/blank values)
        $this->set('subject', ''); 
        $this->set('sendstatus', 0); 
        $this->set('recipients', 0);
        $this->set('sent', 0);
        $this->set('started', 0); 
		$this->set('finished', 0);
	}

	/**
	 * Set a value to the MessageMeta object
	 *
	 */
	public function set($key, $value) {
		if ($key == 'page') {
			$this->page = $value;
			return $this;

		} elseif ($key == 'subject') {
			// regular text sanitizer
			$value = $this->sanitizer->text($value);

		} elseif ($key == 'started' || $key == 'finished') {
			// convert date string to unix timestamp
			if ($value && !ctype_digit("$value")) $value = strtotime($value); 	

			// sanitized date value is always an integer
			$value = (int) $value;
		}

		return parent::set($key, $value); 
	}

	/**
	 * Retrieve a value from the MessageMeta object
	 *
	 */
	public function get($key) {
		$value = parent::get($key); 

		// if the page's output formatting is on, then we'll return formatted values
		if ($this->page && $this->page->of()) {

			if ($key == 'subject') {
				// return entity encoded versions of string
				$value = $this->sanitizer->entities($value); 
			} elseif ($key == 'started' || $key == 'finished') {
				// format a unix timestamp to a date string
				$value = date($this->dateFormat, $value); 				
			}

		}
		
		return $value; 
	}

	/**
	 * Provide a default rendering for a MessageMeta object
	 *
	 */
	public function renderMessageMeta() {
		// remember page's output formatting state
		//$of = $this->page->of();

		// turn on output formatting for our rendering (if it's not already on)
		//if (!$of) $this->page->of(true);

		$out = "
		<p>
		$this->subject<br>
		$this->sendstatus<br>
		$this->recipients<br>
		$this->sent<br>
		$this->started<br>
		$this->finished
		</p>
		";

		//if (!$of) $this->page->of(false); 
		return $out; 
	}

	/**
	 * Return a string representing this MessageMeta object
	 *
	 */
	public function __toString() {
		return $this->renderMessageMeta();
	}

}

 

InputfieldMessageMeta.module.php

Spoiler

<?php namespace ProcessWire;

/**
 * Inputfield that provides form fields for GroupMailer FieldtypeMessageMeta
 * (some of the form fields are type="hidden")
 *
 */

class InputfieldMessageMeta extends Inputfield {

    public static function getModuleInfo() {
        return array(
            'title' => 'GroupMailer MessageMeta',
            'version' => 1,
            'summary' => 'Provides form input for the GroupMailer MessageMeta Fieldtype (some fields not visible!).',
            'requires' => 'FieldtypeMessageMeta',  // needed to be uninstalled with FieldtypeMessageMeta!
        );
    }

    const subjectDefaultMaxlength = 2048;

    protected $page;    
    protected $field;

    /**
     * Construct
     * 
     * @throws WireException
     * 
     */
    public function __construct() {
        parent::__construct();
        $this->setAttribute('maxlength', $this->getSubjectDefaultMaxlength()); 
        $this->setAttribute('placeholder', '');
        $this->set('requiredAttr', 0);
        $this->set('stripTags', false); // strip tags from input?

        // if multi-language, support placeholders for each language
        $languages = $this->wire('languages');
        if ($languages) foreach ($languages as $language) {
            // set to blank value so that Field::getInputfield() will recogize this setting is for InputfieldText 
            if (!$language->isDefault()) $this->set("placeholder$language", '');
        }
    }

    /**
     * Initialize this Inputfield
     *
     */
    public function init() {
        parent::init();
        require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'MessageMeta.php'; 
    }

    public function setPage(Page $page) {
        $this->page = $page; 
    }

    public function setField(Field $field) {
        $this->field = $field;
    }

    /**
     * Get the default maxlength attribute value for subject
     * 
     * @return mixed
     * 
     */
    public function getSubjectDefaultMaxlength() {
        return self::subjectDefaultMaxlength;
    }

    /**
     * @param array|string $key
     * @param array|int|string $value
     * @return $this
     * @throws WireException
     */
    public function setAttribute($key, $value) {
        if ($key == 'value' && !$value instanceof MessageMeta && !is_null($value)) {
            throw new WireException('Value should be an instance of MessageMeta');
        }

        return parent::setAttribute($key, $value);
    }

    /**
     * Render the entire input area for MessageMeta
     * 
     * @return string
     *
     */
    public function ___render() {

        $id = $this->attr('id'); 
        $name = $this->attr('name'); 
        $messagemeta = $this->attr('value');

        $subject = $this->sanitizer->entities($messagemeta->subject);
        //$started = $messagemeta->started > 0 ? date(MessageMeta::dateFormat, $messagemeta->started) : '';
        //$finished = $messagemeta->finished > 0 ? date(MessageMeta::dateFormat, $messagemeta->finished) : '';

        $out = "
        <input type='text' id='_{$id}_subject' name='_{$name}_subject' value='{$subject}' class='uk-input'>
        <input type='hidden' id='_{$id}_sendstatus' name='_{$name}_sendstatus' value='{$messagemeta->sendstatus}'>
        <input type='hidden' id='_{$id}_recipients' name='_{$name}_recipients' value='{$messagemeta->recipients}'>
        <input type='hidden' id='_{$id}_sent' name='_{$name}_sent' value='{$messagemeta->sent}'>
        <input type='hidden' id='_{$id}_started' name='_{$name}_started' value='{$messagemeta->started}'>
        <input type='hidden' id='_{$id}_finished' name='_{$name}_finished' value='{$messagemeta->finished}'>
        ";

        return $out;
    }

    /**
     * Process the input after a form submission
     *
     * @param WireInputData $input
     * @return $this
     * @throws WireException
     * 
     */
    public function ___processInput(WireInputData $input) {

        if (!$this->page || !$this->field) {
            throw new WireException("This inputfield requires that you set valid 'page' and 'field' properties to it."); 
        }

        $name = $this->attr('name'); 
        $messagemeta = $this->field->type->getBlankValue($this->page, $this->field); 

        //if (!isset($input->$name)) return $this; // TODO: ???

        $messagemeta = new MessageMeta();
        $messagemeta->subject = $input["_{$name}_subject"];
        $messagemeta->sendstatus = $input["_{$name}_sendstatus"];
        $messagemeta->recipients = $input["_{$name}_recipients"];
        $messagemeta->sent = $input["_{$name}_sent"];
        $messagemeta->started = $input["_{$name}_started"];
        $messagemeta->finished = $input["_{$name}_finished"];
        
        // if the processed MessageMeta subject is different from the previous,
        // then flag this Inputfield as changed so that it will be automatically saved with the page
        // TODO: ???
        if ($messagemeta->subject != $this->value->subject) {
            $this->attr('value', $messagemeta); 
            $this->trackChange('value'); 
        }

        return $this;
    }

    /**
     * Output API Notes
     *
     * @return $inputfields
     */
    public function ___getConfigInputfields() {
        $inputfields = parent::___getConfigInputfields();

        /** @var InputfieldMarkup $field */
        $field = $this->modules->get('InputfieldMarkup'); 
        $field->label = $this->_('API Notes'); 
        $field->description = $this->_('Individual values from this field can be get using the following from your template files:');
        $field->value = 
            "<pre>" .
            "\$page->{$this->name}->subject" . PHP_EOL .
            "\$page->{$this->name}->sendstatus" . PHP_EOL .
            "\$page->{$this->name}->recipients" . PHP_EOL .
            "\$page->{$this->name}->sent" . PHP_EOL .
            "\$page->{$this->name}->started" . PHP_EOL .
            "\$page->{$this->name}->finished" . PHP_EOL .
            "</pre>";
    
        $inputfields->add($field); 

        return $inputfields; 	
    }

}

 

 

 

 

Share this post


Link to post
Share on other sites

I'm not really getting what the question is, although I haven't looked at your code in full.

4 hours ago, Gadgetto said:

Comparing with other multi-language fields doesn't help because they are all single-value fields.

Are you sure about this? What do you mean by multi-language fields? Maybe install the multi-lingual site profile and see how things work? For instance, the field images works fine as a multi-language field and it is a multi-value field. I think I'm just not getting the question.

4 hours ago, Gadgetto said:

Here are the full sources of my multi-value input field:

if you Fieldtype is meant to store multiple values for the same page (meaning multiple rows in its database table for the same field on the same page) , then this code is not correct:

4 hours ago, Gadgetto said:

class FieldtypeMessageMeta extends Fieldtype {

In that case, you'll need to extend FieldtypeMulti. However, I may have misunderstood what you mean by multiple values. For instance, if you meant multiple database columns on a single db row, that's something different.

  • Like 1

Share this post


Link to post
Share on other sites
2 hours ago, Gadgetto said:

Here are the full sources of my multi-value input field:

Hi @Gadgetto I am sorry for not being able to help but could you please make your thread more readable by "hiding" long code in "Spoiler" blocks (which can be added by clicking on the "eye icon" in the toolbar of this RTE)? 

Share this post


Link to post
Share on other sites
10 hours ago, szabesz said:

Hi @Gadgetto I am sorry for not being able to help but could you please make your thread more readable by "hiding" long code in "Spoiler" blocks (which can be added by clicking on the "eye icon" in the toolbar of this RTE)? 

Sorry for that, I was indeed searching for the "Reveal hidden contents" feature as I saw this in other post. Just couldn't find it. Thanks!

  • Thanks 1

Share this post


Link to post
Share on other sites
12 hours ago, kongondo said:

Are you sure about this? What do you mean by multi-language fields? Maybe install the multi-lingual site profile and see how things work? For instance, the field images works fine as a multi-language field and it is a multi-value field. I think I'm just not getting the question.

I don't know how to describe it better. I thought multi-language field is a common term for fields which provide input for different languages (like the InputfieldText/InpufieldTextLanguage).

12 hours ago, kongondo said:

if you Fieldtype is meant to store multiple values for the same page (meaning multiple rows in its database table for the same field on the same page) , then this code is not correct:

My Fieldtype is a multi-columns field not multi-rows, therefore FieldtypeMulti won't work (as it is only for multi-row fields).

As I explained in my initial post, I created a Fieldtype which has these columns:

$schema['data'] = 'text NOT NULL'; // we're using 'data' to represent our 'subject' field
$schema['sendstatus'] = 'tinyint NOT NULL DEFAULT 0'; // message send status
$schema['recipients'] = "int(10) unsigned NOT NULL DEFAULT 0";  // recipients counter
$schema['sent'] = "int(10) unsigned NOT NULL DEFAULT 0";  // sent counter
$schema['started'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending start
$schema['finished'] = "int(10) unsigned NOT NULL DEFAULT 0";  // message sending finished

The "data" col is a text field and I'd like to provide language support for this col only! (the other columns send status, recipients, ... arte integer fields and don't need language support).

I simply can't find an example Fieldtype which provides similar functionality.

Share this post


Link to post
Share on other sites
3 hours ago, Gadgetto said:

I simply can't find an example Fieldtype which provides similar functionality.

There is one actually, sort of. Look at the 'images' field in a multilingual setup. 

As you are aware, for truly (I use this word reservedly) multilingual fields, each language has to have its own column. That makes it easy to search the columns in the language you want. However, in your case, since you want only one column to have multilingual features, you have two choices ( + a 3rd not very good one):

  1. Go the route of images fields. In a multilingual setup, the description column of an image field holds each languages' value, saved as JSON. E.g. {"0":"We're gonna need a bigger terminal.","1012":"Wir brauchen einen größeren Terminal.","1013":"Me tarvitsemme isomman päätteen."}. The index of the values are the language ID. In this case, 0= English, 1012=German and 1013=Finnish.The trade off here is searching in one language is limited.
  2. Change your database design pattern. No need to cram things in if they don't fit 😎. Let your subject be its own multilingual field and let your other single value data live in their own non-multilingual field. Nothing wrong with that. 
  3. I mention this 3rd option hesitantly. Stick with one field as your are doing but for your data (subject) column create a lookup table for other languages. I am no DB guru but the little I know tells me this 3rd option is not a good design at all.
Edited by kongondo
  • Like 2

Share this post


Link to post
Share on other sites

@kongondo Thank you for your hints. In general, I'd like to follow the standard behavior of ProcessWire as closely as possible. So having own "data" columns for each  language would be the best.  I have a Fieldtype "FieldtypeMessageMeta" and try to extend it with "FieldtypeMessageMetaLanguage" - just like other language capable field types do. So an admin can switch between normal Fieldtype and language Fieldtype if necessary.

the longer I think about it, the more uncertain I am if the message subject has to be multilingual at all.

Is it common to automatically send multilingual newsletters? Or is it better to send a separate newsletter for each language?

 

Share this post


Link to post
Share on other sites
4 hours ago, Gadgetto said:

Is it common to automatically send multilingual newsletters? Or is it better to send a separate newsletter for each language?

I might have seen a multilingual newsletter once in my life but I might be wrong, so in my experience multilingual newsletters are very rare.

  • Like 2

Share this post


Link to post
Share on other sites
1 minute ago, szabesz said:

I might have seen a multilingual newsletter once in my life but I might be wrong, so in my experience multilingual newsletters are very rare.

I also think so. So I'll let the field be single-language for now and if there are requests to add multilingual feature it can be added later.

  • Like 2

Share this post


Link to post
Share on other sites
On 2/27/2019 at 5:34 PM, Gadgetto said:

So I'll let the field be single-language for now and if there are requests to add multilingual feature it can be added later.

Just had a thought, so revisiting this. Going by your screenshot, it seems to me that each message is a unique page, no? If that is the case then you may have your cake and eat it too if instead of storing a message's subject in the 'data' column of your Fieldtype, let it be the page's title? This means for multilingual sites, they can decide to have multilingual messages by using language field title and language textarea (assuming that is where the message body is). You would then need to use 'data' to store something else.

 

Just my 2p.

Share this post


Link to post
Share on other sites
12 hours ago, kongondo said:

Just had a thought, so revisiting this. Going by your screenshot, it seems to me that each message is a unique page, no? If that is the case then you may have your cake and eat it too if instead of storing a message's subject in the 'data' column of your Fieldtype, let it be the page's title? This means for multilingual sites, they can decide to have multilingual messages by using language field title and language textarea (assuming that is where the message body is). You would then need to use 'data' to store something else.

You are right! Each message is a unique page. To be flexible, I decided not to use the title field as email subject. GroupMailer will be designed so that each page can be a message. It will only be necessary to add a custom field (FieldtypeMessageMeta) to the corresponding template.
This field type will include the subject field. So it is possible that pages have separate titles and email subject.

Share this post


Link to post
Share on other sites
25 minutes ago, Gadgetto said:

GroupMailer will be designed so that each page can be a message. It will only be necessary to add a custom field (FieldtypeMessageMeta) to the corresponding template.
This field type will include the subject field. So it is possible that pages have separate titles and email subject.

Gotcha.

Share this post


Link to post
Share on other sites

Based on your description I feel like you're tackling the problem from the wrong end. You're actually trying to do what was requested from MarkupSEO ages ago: Just adding a single field to a template, but getting a whole bunch of fields in the process. In my opinion either let people add all fields to their templates  or even better have an own template and just store the metadata there. Sadly it took quite a few month until the latter option was actually added properly, but now we have it: 

https://processwire.com/blog/posts/processwire-3.0.73-and-new-fieldset-types/#fieldset-page-fieldtypefieldsetpage
https://processwire.com/blog/posts/processwire-3.0.74-adds-new-fieldsetpage-field-type/

Edit:

And some more context from the time I initially proposed the idea (I've no idea if Ryan actually knew this, but it's basically what I proposed): 

 

  • Like 2

Share this post


Link to post
Share on other sites

@LostKobrakai FieldtypeFieldsetPage looks great - didn't even know this exists! Thanks for this hint!

Using a template for configuring/defining a GroupMailer message page was my initial intention. But wouldn't that be too inflexible? Wouldn't it be better if the dev could use any template he likes just by adding a single field? It would also allow to "convert" any existing page to a GroupMailer Message on the fly.

I'd like to hold the Module as flexible as possible but also easy to setup. The longer I work on the project, the more uncertain I will be about the implementation...

Share this post


Link to post
Share on other sites

That's what FieldtypeFieldsetPage would allow you to do. Create the field on installation of your module (and update/delete it from your module; It's not to be edited by the user) and the user would just need to add that field to whatever template needed.

Share this post


Link to post
Share on other sites
Just now, LostKobrakai said:

That's what FieldtypeFieldsetPage would allow you to do. Create the field on installation of your module (and update/delete it from your module; It's not to be edited by the user) and the user would just need to add that field to whatever template needed.

I need to have a look at this Fieldtype and how this is implemented. I currently don't fully understand how this is meant to be used.

Share this post


Link to post
Share on other sites

It's basically like a repeater, but limited to a single page, which you don't need to explicitly create.

Share this post


Link to post
Share on other sites

GroupMailer needs a lot of meta data like some send counters, status-flags, send/finished dates and so on - all meta fields are hidden fields only and not editable by the user. The custom Fieldtype I created has all these columns packed in one field. Using  FieldtypeFieldsetPage would mean that I create separate fields for each of these meta columns (if I understand right). Wouldn't this have a performance impact. Instead of using a single (multi-column) field I'd need to use many separate fields.

Share this post


Link to post
Share on other sites

Sure has, but it's what you do with most everything else in processwire as well. Are the read/write patterns really that demanding that it would matter? 

But if only one field of yours is multi-language I feel like it should be quite possible to adapt existing patterns from other fieldtypes. 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By Gadgetto
      I'm looking for an ON/OFF Inputfield for my GroupMailer module I'm currently writing. (Preferably based on a core field, not 3rd party)  
      I'd like to implement a quick ON/OFF switch to quickly enable/disable all sending process (an emergency stop!). The field should submit it's value immediately after state changes.
      Maybe a checkbox toggle or a kind of button which is able to change it's state.
      Found this forum thread for changing a checkbox field to toggle style with CSS - but it's only for UIKit admin theme:
      What would you recommend?
      Greetings,
      Martin
    • By Gadgetto
      Hi,
      I'm starting the port of my MODX add-on GoodNews - a powerful integrated group and newsletter mailing system - to Processwire:
      http://www.bitego.com/extras/goodnews/
      I know I'll have a long and hard way to go... 🙂
      In order to plan the basic principles for my module in advance, here is my first question (more will come, that's for sure):
      GoodNews Subscribers are basically MODX users extended with the necessary meta informations to handle subscriptions and other mailing related stuff. The benefit for a subscriber being a MODX user is that it I also can use the permission system of MODX. How should I handle this in Processwire? Should subscribers also be Processwire users? Or is it better to use a custom user type? How will processwire handle lists of thousands of users?
      Thanks in advance for your help!
      Greetings,
      Martin
    • By Gadgetto
      Under MODX my Newsletter Add-On manages all mailings in its own resource containers. This is comparable to a PW page, which serves as a container for subpages.
      My newsletter module GroupMailer will also provide an administration console where you can start, stop and generally manage mailings + allows you to watch the sending status.
      What would you recommend as an experienced PW user? How should the mailings be managed in the PW module? I want to follow the PW paradigms as much as possible and also offer the greatest possible flexibility.
      Here is a screenshot from the MODX version:

      Greetings, and a Happy New Year!
      Martin
    • By Harmen
      I want to add a few pages to an AsmSelect Page field inside a repeater using the following code:
      $trialsPage = wire("pages")->get(28422); // Get the page $trialsPage->of(false); $newTrial = $ordersPage->trial_repeater_orders->getNewItem(); // Add item to repeater foreach ($selectedProducts as $selectedProduct){ $productPage = $pages->get("template=product, reference=$selectedProduct"); $newTrial->trial_selected_products->add($productPage); } $newTrial->save(); $trialsPage->save(); However, when I check the page where the field is located it doesn't have the new values as expected. The selected pages exist, the field is in the right location, made sure that the output formatting is turned off: $page->of(false); But it still doesn't work with a variable. No matter what I try, it doesn't work.
      It only works when I replace $selectedProduct with a hardcoded string. Am I doing something wrong here?
    • By Sebi2020
      Hey, I'm new and I created a simple module for tagging pages because I didn't found a module for it (sadly this is not a core feature). This module is licensed under the GPL3 and cames with absolutly no warranty at all. You should test the module before using it in production environments. Currently it's an alpha release. if you like the module or have ideas for improvements feel free to post a comment. Currently this fieldtype is only compatible with the Inputfield I've created to because I haven't found  an Inputfield yet, that returns arrays from a single html input.
      Greetings Sebi2020
      FieldtypeTags.zip.asc
      InputfieldTagify.zip
      InputfieldTagify.zip.asc
      FieldtypeTags.zip
×
×
  • Create New...