Jump to content

Events Fieldtype & Inputfield (How to make a table Fieldtype/Inputfield)


ryan

Recommended Posts

Error: Exception: Method Event::addStatus does not exist or is not callable in this context (in /var/www/web1807/html/gs/wire/core/Wire.php line 320)

the same for

$event->delete();

or

$page->events->delete($event);

i've looked in the cheatsheet since i've thought that eventsfield is a repeater and repeaters are like pages.....?

How could i get an item removed via the API

looked into the module itself there is no methode in EventArray.php and in the inputfieldEvents.module i did'nt found something usefull....

Link to comment
Share on other sites

GET IT.....i can't say how much i like to explore PW!

like everything i learn about the api ->it->is->really->in->most->cases->easier->than->i->think...

//deletes events that older than today
	foreach($page->events as $e) {
		// get date
		$ed = $e->date;
		//check against today
		if(time() > $ed) {
			$page->of(false);
                        //remove is the right way from wirearray
			$page->events->remove($e);
			$page->save();
		}
	}

thank you dave for the hint!

  • Like 2
Link to comment
Share on other sites

  • 1 month later...

Im having an issue when trying to create a simple field of type 'Table' (see attached image)

Receive the error "Field: Method to call is required and was not specified (toMethod)"

Has anyone seen this? 

I'm running version PW 2.5.2, I've got debug mode running but not even sure what I should be looking for. I've purchased ProFields as well...

ProFields: Multiplier: 0.0.3

ProFields: Table: 0.0.5

ProFields: Textareas: 0.0.3

When I try to check for an update for ProFields: Table I also get an error "Session: Error reported by web service: Unable to find that module"

Any help appreciated.

Thanks again

post-2748-0-55858700-1415237927_thumb.jp

Link to comment
Share on other sites

@Chris White,

I would post any issues about ProFields on the ProFields forum.

Also, make sure that you have fully installed the ProFields and see if that error persists.

Regarding the upgrade, i'm not sure that you can update ProFields from the admin;

Once you make the table field, you do need to configure the column names and types

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Hi Chaps,

I have added an extra field to the event field type for URL.

Everything works fine except for if I try to delete the URL or whatever text is in the field and re-save the page, on page reload the URL is still present.

However if I remove the value from say 'location' or 'notes' and then delete the value from the 'URL' field too and then re-save then it removes the value from 'location' and 'url' as it should.

Is there anything that stands out as obvious for why it is doing this straight off the cuff?

I can't fathom it. All I have done is duplicated the necessary information in event.php, FieldtypeEvents.module and InputfieldEvents.module

Thanks in advance for any help!

Alex

Link to comment
Share on other sites

Hello,

Anyone tried setting an Events field visibility to "Locked, value visible but not editable"? After I did that, I am getting raw HTML:

<p><strong>2014-04-22</strong><br /><em>Moon</em><br />Moon party!</p>

Is there a way to have plain values displayed without getting into the module code?

Simple way around it: Add this to the InputfieldEvents class. 

	public function ___renderValue(){
		return $this->attr('value');
	}

This gets rid of the standart entity encoding, so you need to encode the eventdata by yourself in the renderEvent() of the Event class.

	public function renderEvent() {
		// 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);
		$date = $this->sanitizer->entities($this->date);
		$location = $this->sanitizer->entities($this->location);
		$notes = $this->sanitizer->entities($this->notes);
		$out = "<p><strong>$date</strong><br /><em>$this->location</em><br />$notes</p>";	
		if(!$of) $this->page->of(false); 
		return $out; 
	}
Link to comment
Share on other sites

  • 2 months later...

I dove into customizing this module the other day and was looking for a little help in understanding the database schema. The current module sets that date as a unix time stamp and saves this in the data column in the database. I don't need a date column, so I converted it to text to be saved in the data column. Using $schema['data'] = 'text NOT NULL'; only throws an sql error. Apparently mysql wants a defined number to index against, which makes sense.

After looking at the FieldtypeText.module I found the below.

$schema['keys']['data_exact'] = 'KEY `data_exact` (`data`(255))';
$schema['keys']['data'] = 'FULLTEXT KEY `data` (`data`)';

Could someone advise on what the "data_exact" is, or add any clarification on the above? I understand the 'key', but not sure why both are required. Thanks in advance.

Link to comment
Share on other sites

@kongondo

What a post. Seemingly crowed by arrows, but after a few looks so full of info.

Are there any good tutorials out there about this topic? A few month ago I had my problems to enable fulltext search in one of my modules and I would like to know a little more about this. Preferably without learning SQL in it's entirety.

  • Like 2
Link to comment
Share on other sites

Wow kongondo,

I was out of town and read the response notification on email, but just now saw your diagram in your reply. The fact that you broke this down and described it like you did speaks volumes of your character. This diagram should be added to the docs its so descriptive and informative. Thank you very much for taking the time to explain.

  • Like 2
Link to comment
Share on other sites

  • 5 months later...

Hopefully someone will still read this :)

Can someone please explain why it is better to use pages for events solution? The main advantage I see here is that this solution is much more user-friendly. If I want that the site will be maintained (data-wise) by a non-developer , I think it is much easier for a non-developer to add events using this plug-in and not open and delete new pages every time he has new events. 

Any thoughts on the advantages of pages?

Link to comment
Share on other sites

Some kind words on the "pages" topic....

https://processwire.com/talk/topic/2296-confused-by-pages/

https://processwire.com/talk/topic/8374-how-to-approach-this-feat-in-processwire/#entry81113

i've used the event fieldtype for a little eventcal - but i regret this decision since i've thought like in my former cms i need a module to solve such a task....but hey this is Processwire so i better didn't and used system build in concepts to use pages as intended from Rayn for almost everything.

some advatages for events:

- easy to extend just a little example for eventpages and adding a new feature to events

(https://processwire.com/talk/topic/9738-proof-of-concept-a-page-countdown-widget/)

- easy to administrate, too - hey it is just a Pagetree about events - if you setup the template and field right editing should stay easy - or use some fieldtype for the editing stuff of the childpages like PageTable or BatchChildEditor to get a better UI for this

- easy to use

- updateproof

.....

as a new easy editing option for your users you could setup pages for events and let users do the editing via Lister Pro module from Ryan since the actual version has inline editing, too - this could be a great alternative on this topic.

https://processwire.com/api/modules/lister-pro/

http://processwire.com/blog/posts/inline-ajax-page-editing-comes-to-listerpro-processwire-2.6.6/

i think if you buy Lister Pro and combine this editing features with a good pagesetup you could provide very very good and easy to use editing pages and overviews on events, products, documents, images or something else (for eg i use it for little visitor stats and image overview for my users...)

if i should create a eventcal again i would go this path with pages and Lister Pro.

But this is just a advice - the better is as always the enemy of the good.....;)

Best regards mr-fan

Link to comment
Share on other sites

And I think it depends on the complexity of your events or whatever you're building ;-)

I extended the Events Fieldtype for one project to have some extra fields and colors.

Link to comment
Share on other sites

  • 1 month later...

I modified the module for my needs, So I now have 5 fields, date, timeofday, location, showname, and link. 

The table is presented correctly on the admin page, but there are a few problems:

the "location" and "showname" fields are being saved and can be accessed, but are not being displayed on the table. 

the "timeofday" and "link" fields are not being saved to the DB. 

Here are the modified files:

Event.php

<?php

/**
 * An individual event item to be part of an EventArray for a Page
 *
 */
class Event extends WireData {

	const dateFormat = 'Y-m-d'; 

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

	/**
	 * Construct a new Event
	 *
	 */
	public function __construct() {

		// define the fields that represent our event (and their default/blank values)
		$this->set('date', 0);
		$this->set('timeofday', '00:00');
		$this->set('location', ''); 
		$this->set('showname', '');
		$this->set('link', '');
	}

	/**
	 * Set a value to the event: date, location or notes
	 *
	 */
	public function set($key, $value) {

		if($key == 'page') {
			$this->page = $value; 
			return $this; 

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

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

		} else if($key == 'location' || $key == 'showname' || $key == 'link' || $key == 'timeofday') {
			// regular text sanitizer
			$value = $this->sanitizer->text($value); 
		}

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

	/**
	 * Retrieve a value from the event: date, location or notes
	 *
	 */
	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 == 'date') {
				// format a unix timestamp to a date string
				$value = date(self::dateFormat, $value); 				

			} else if($key == 'location' || $key == 'notes' || $key == 'location' || $key == 'timeofday') {
				// return entity encoded versions of strings
				$value = $this->sanitizer->entities($value); 
			}
		}

		return $value; 
	}

	/**
	 * Just for fun: returns true if the event has already past
	 *
	 */
	public function isPast() {
		if($this->date < time()) return true;
		return false; 
	}

	/**
	 * Provide a default rendering for an event
	 *
	 */
	public function renderEvent() {
		// 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><strong>$this->date</strong><br /><em>$this->location</em><br />$this->notes</p>";	
		if(!$of) $this->page->of(false); 
		return $out; 
	}

	/**
	 * Return a string representing this event
	 *
	 */
	public function __toString() {
		return $this->renderEvent();
	}

}

InputfieldEvents.module:
<?php

class InputfieldEvents extends Inputfield {

	public static function getModuleInfo() {
		return array(
			'title' => 'Events',
			'version' => 3,
			'summary' => 'Input field for events.',
			'requires' => 'FieldtypeEvents', 
			);
	}

	protected $page;
	protected $field; 

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

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

	/**
	 * Render a table row for an individual Event input
	 *
	 */
	protected function renderRow(Event $event, $cnt, $class = 'Event') {

		$name = $this->attr('name');
		$date = $event->date > 0 ? date(Event::dateFormat, $event->date) : '';
		$location = $this->sanitizer->entities($event->location); 
		$notes = $this->sanitizer->entities($event->notes); 

		$out = "
			<tr class='Event$cnt $class'>
				<td><a href='#' class='EventClone'><span class='ui-icon ui-icon-copy'></span></a></td>
				<td><input type='text' name='{$name}_date[]' value='$date' class='datepicker' /></td>
				<td><input type='text' name='{$name}_timeofday[]' value='$timeofday' /></td>
				<td><input type='text' name='{$name}_location[]' value='$location' /></td>
				<td><input type='text' name='{$name}_showname[]' value='$showname' /></td>
				<td><input type='text' name='{$name}_link[]' value='$link' /></td>
				<td>
					<a href='#' class='EventDel ui-helper-clearfix'><span class='ui-icon ui-icon-trash'></span></a>
					<input type='hidden' name='{$name}_del[]' value='0' />
				</td>
			</tr>
			";

		return $out; 
	}

	/**
	 * Render the entire input area for Events
	 *
	 */
	public function ___render() {

		// get Event template for creating new events
		$tbody = $this->renderRow(new Event(), 0, 'Event EventTemplate'); 
		
		// render rows for existing Events	
		foreach($this->attr('value') as $cnt => $event) {
			$tbody .= $this->renderRow($event, $cnt); 
		}	

		$out = 	"
		<table class='InputfieldEvents'>
			<thead>
			<tr class=''>
				<th class='EventClone'> </th>
				<th class='EventDate'>Date</th>
				<th class='EventTimeofday'>Time</th>
				<th class='EventLocation'>Location</th>
				<th class='EventShowname'>Show name</th>
				<th class='EventLink'>Link</th>
				<th class='EventDel'>
					<a title='Delete All' href='#' class='EventDel'><span class='ui-icon ui-icon-trash'></span></a>
				</th>
			</tr>
			</thead>
			<tbody>
			$tbody
			</tbody>
		</table>
		";

		// add a button that triggers adding a new event
		$btn = $this->modules->get('InputfieldButton');
		$btn->attr('id', $this->attr('name') . "_new"); 
		$btn->class .= " InputfieldEventsAdd";
		$btn->icon = 'plus-sign';
		$btn->value = $this->_('Add New Event'); 
		$out .= $btn->render();

		return $out; 
	}

	/**
	 * Process input for the Events inputs
	 *
	 */
	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'); 
		$events = $this->field->type->getBlankValue($this->page, $this->field); 
		$numDeleted = 0; 

		foreach($input->{"{$name}_date"} as $cnt => $date) {

			if(!$cnt) continue; // template for 'new' item is the first, so we can ignore it

			// check if the item is being deleted
			if($input->{"{$name}_del"}[$cnt]) {
				// if being deleted, then we simply skip over it
				$numDeleted++;
				continue; 
			}

			// create the $event and add it to our $events
			$event = new Event();
			$event->date = $date; 
			$event->timeofday = $input->{"{$timeofday}_timeofday"}[$cnt];
			$event->location = $input->{"{$name}_location"}[$cnt];
			$event->showname = $input->{"{$name}_showname"}[$cnt];
			$event->link = $input->{"{$link}_link"}[$cnt];
			$events->add($event); 
		}

		// if the string values of the processed events are different from the previous,
		// or if any events have been deleted, then flag this Inputfield as changed
		// so that it will be automatically saved with the page
		if("$events" != "$this->value" || $numDeleted) {
			$this->attr('value', $events); 
			$this->trackChange('value'); 
		}
	}
}

FieldtypeEvents.module:
<?php

/**
 * A field that stores events each with a date, location and notes
 * 
 * This is an example of creating your own Fieldtype to represent a spreadsheet type table of information.
 *
 */

class FieldtypeEvents extends FieldtypeMulti {

	public static function getModuleInfo() {
		return array(
			'title' => 'Events',
			'version' => 3,
			'summary' => 'Field that stores a table of events for a page.',
			'installs' => 'InputfieldEvents', 
			);
	}

	/**
	 * Initialize this Fieldtype
	 *
	 */
	public function init() {
		parent::init();
		$dir = dirname(__FILE__);
		require_once("$dir/Event.php"); 
		require_once("$dir/EventArray.php"); 
	}

	/**
	 * Return the required Inputfield used to populate a field of this type
	 *
	 */
	public function getInputfield(Page $page, Field $field) {
		$inputfield = $this->modules->get("InputfieldEvents"); 

		// our inputfield requires a Page and Field (most Inputfields don't)
		$inputfield->setPage($page);
		$inputfield->setField($field); 

		return $inputfield; 
	}

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

        /**
         * Given a raw value (value as stored in DB), return the value as it would appear in a Page object
	 *
         * @param Page $page
         * @param Field $field
         * @param string|int|array $value
         * @return string|int|array|object $value
         *
         */
        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 EventArray) return $value; 

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

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

		// create new Event objects from each item in the array
		foreach($value as $v) {
			$event = new Event();
			$event->date = $v['data']; // note we're converting 'data' to 'date'
			$event->timeofday = $v['timeofday'];
			$event->location = $v['location'];
			$event->showname = $v['showname'];
			$event->link = $v['link'];
			$event->setTrackChanges(true); 
			$events->add($event); 
		}

		$events->resetTrackChanges(); 

                return $events;  
        }

        /**
         * Given an 'awake' value, as set by wakeupValue, convert the value back to a basic type for storage in DB. 
         *              
         * @param Page $page
         * @param Field $field
         * @param string|int|array|object $value
         * @return string|int
         *
         */
        public function ___sleepValue(Page $page, Field $field, $value) {

		$sleepValue = array();

		// if we are given something other than an EventArray, 
		// then just return a blank array
		if(!$value instanceof EventArray) return $sleepValue; 

		// make the events sort by date ascending
		$value->sort('date'); 

		// convert each Event to an array within sleepValue
		foreach($value as $event) {
			$sleepValue[] = array(
				'data' => (int) $event->date, // note: date is becoming data 
				'timeofday' => $event->timeofday,
				'location' => $event->location, 
				'showname' => $event->showname,
				'link' => $event->link
				); 
		}
		return $sleepValue;
        }

	/**
	 * Given a value, make it clean for storage within a Page
	 *
	 */
	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 EventArray, throw an error
		if(!$value instanceof EventArray) {
			throw new WireException("Value set to field '$field->name' must be an EventArray"); 
		}

		// note that sanitization of individual fields within a given event is already 
		// performed by the Event::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 Event object
		// is doing this work in the Event::get() method. But I've included this
		// comment here just to explain where that is taking place. 
		return $value; 
	}

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

		// 'data' is a required field for any Fieldtype, and we're using it to represent our 'date' field
		$schema['data'] = 'INT NOT NULL DEFAULT 0'; 

		// our text fields
		$schema['timeofday'] = 'TEXT NOT NULL';
		$schema['location'] = 'TINYTEXT NOT NULL'; 
		$schema['showname'] = 'TEXT NOT NULL';
		$schema['link'] = 'TEXT';

		// indexes, for any fields that need to be searchable from selectors
		// in this case, we're just making our 'date' field searchable
		// but the others could easily be added, likely as fulltext indexes
		$schema['keys']['data'] = 'KEY data(data)'; 

		return $schema; 
	}

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

		// If searching 'date' then assume our default (data) field 
		if($subfield == 'date') $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); 
	}


}

Can anyone see what the problem is?

Edited by LostKobrakai
Added spoiler for the pasted codes
Link to comment
Share on other sites

To add fields you need to change various places: sleepValue(), wakeupValue(), sanitizeValue() and the database-something functions in the Fieldtype and the render() function in the inputfield. Also you need to reinstall the module (or at least the fields) after changes to the database structure function, as existing tables won't be updated.

Link to comment
Share on other sites

  • 1 month later...
I’m having a hard time getting started with module development in Processwire. I’m trying to implement a schedule for sports teams, which is why I stumbled across this module. The problem is, that I need events to be hierarchical, as an event could as well be a game or a tournament, which would then again consist of multiple games. So far, I wasn’t able to find out how to implement this hierarchy. Or should I rather work with the Admin Custom Pages Module?

I would be thankful, if someone could point me in the right direction.

Link to comment
Share on other sites

I’m having a hard time getting started with module development in Processwire. I’m trying to implement a schedule for sports teams, which is why I stumbled across this module. The problem is, that I need events to be hierarchical, as an event could as well be a game or a tournament, which would then again consist of multiple games. So far, I wasn’t able to find out how to implement this hierarchy. Or should I rather work with the Admin Custom Pages Module?
 
I would be thankful, if someone could point me in the right direction.

I would make this with regular PW pages (events), and then selectable options for game or tournament (asm select would allow both to be selected).

if the event is a tournament, then you show a page-table field for child events, which would be similar to the event template but assume they are games (if they are child of an event marked as a tournament)

  • Like 2
Link to comment
Share on other sites

  • 4 months later...
Hi,

im new in the processwire world and i like it! ;)

Thank you for the great module. It was a huge help for me.

I added some fields (I‘m not sure, but I think 18) to the Fieldtype so it fits better for my needs.

I created a page with 6000 courses (events) for a mobile app. These couseses can be filtered by date and locations. (After the filter, there are only 100-200 courses to show) But it takes nearly 3 seconds to load the event-objects.

Btw: I already reduced the time vom 7 seconds to 3 by removing the strtotime-functions in the module and setting MySQL-Indexes.

Does anybody have any idea to get it faster?

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...