Jump to content

Recommended Posts

This is related to the Wireframe::page() static utility method (https://wireframe-framework.com/docs/static-utility-methods/) but not specifically covered in the docs, I think. This is also one of the things I struggled trying to explain briefly in the changelog 🙂

The general idea is that since Wireframe works through the Alternate Template Filename setting of templates, in earlier versions you couldn't do things like $pages->get(1234)->setLayout(null)->render() and expect the output to come from a Wireframe view UNLESS the Alternate Template Filename for the template this page uses had the alt filename pointing to a Wireframe bootstrap file (i.e. /site/templates/wireframe.php).

After this update the syntax mentioned above will — in most cases at least — work right out of the box. And even in cases where it won't work (such as when you're fetching data from ProcessWire via a command-line script), Wireframe::page(1234)->setLayout(null)->render() will almost certainly work.

Typical use case for me are pages that are not supposed to be directly accessible, like a contact page that lives under a shared "bucket" (e.g. /data/contacts/lastname-firstname/) and is only ever made visible when rendered as part of another page's markup. If I only need to output a few fields from the page I can do that directly in the view file for the containing page, but if I need to reuse this data all over the site, I prefer to define a view file for the contact (e.g. /site/templates/views/contact/embed.php) and render it using that:

<!-- some page that lists contacts -->

<?php foreach ($page->contacts as $contact): ?>
    <?= $contact->setLayout(null)->setView('embed')->render() ?>
<?php endforeach; ?>

Hope this makes more sense 🙂

  • Like 3

Share this post


Link to post
Share on other sites

Ehy @teppo I'm really digging Wireframe and I really think that it's a brilliant work. Thank you also for mentioning my old TemplateEngineBlade module in the list of alternatives.

I'm currently restyling one of my websites and I decided to use Wireframe for the purpose. Everything is going great so far! I also decided to create a renderer module that uses the Blade template engine, because I don't like Twig that much. Check it out https://github.com/mauricius/WireframeRendererBlade

  • Like 3
  • Thanks 1

Share this post


Link to post
Share on other sites

Hi @teppo

First I have to say: WOW! I took another look at your module because I got again frustrated with my setup and I wanted to build something on my own. Luckily I remembered your module and came back to the docs before developing something on my own 🙂 

  • Thx for the docs, they are great! Some suggestions:
    • It would be great to have < prev | next > links on each page at the bottom. I almost missed all the other great pages when reaching the bottom of https://wireframe-framework.com/docs/ (the menu is not visible in the sidebar on such long pages)
    • It would be great to have a simple hello-world walkthrough for setting up a custom wireframe template (instead of providing a full-blown site profile). It's always easier to understand something if you start from scratch than finding your way around several files not knowing which pieces came before and after another.

And then something more advanced and more important: I think it would be great to get a little more (or different) control over where Wireframe does look for files. I read about the config settings, but IMHO they are a little limiting. As far as I understood one can define a path for every type that Wireframe is based on (views, controllers, components, etc). And as far as I understood it is possible for components to define custom view files via Wireframe::component('foo')::setView('bar'); Is that correct?

The problem with that approach is that it is not possible to load components from outside of the wireframe folder structure. That's a quite big deal, because if that were possible, we could ship custom components/views/partials (I'm planning on working on styles using RockLESS) directly within our modules and that would just be awesome! Take this example:

module RockSearch
lives in /site/modules/RockSearch
 |- Wireframe
 |  |- js
 |  |  '- search.js
 |  |- less
 |  |  '- searchform.less
 |  '- partials
 |     '- searchform.php
 '- RockSearch.module.php

What if we had a new Wireframe type called "package"? This could be included in any Wireframe layout like this:

<?php
$css = RockLESS::css([
  $packages->RockSearch->less->searchform.less,
  $packages->MyGreatModule->less->style1.less,
]);
?>
<html>
  <head>
    <link rel="stylesheet" href="<?= $css ?>">
    <?= $packages->RockSEO->partials->meta() ?>
  </head>
  <body>
    <section><?= $partials->header() ?></section>
    <section><?= $packages->RockSearch->partials->searchform() ?></section>
    ...
  </body>
</html>

Finally we'd only need to tell Wireframe about the packages:

$config->wireframe = [
  ...
  'packages' => [
    'RockSearch' => $config->paths->siteModules."RockSearch",
    'RockSEO' => $config->paths->siteModules."RockSEO",
  ],
];

// or something like this
Wireframe::addPackage($config->paths->siteModules."RockSearch");

This would finally bring some standards to the ProcessWire frontend which would make a huge difference regarding reusability! If we found a bug in RockSearch, we could directly fix it in the modules partials which would make it instantly available to all projects using it after a simple modules update!

Thx for your great work again! What do you think?

PS: Defining different views/partials for RockSearch should also be possible making it easy to support different frameworks or framework versions, eg $packages->RockSearch->partials->uikit3() or ->bootstrap4() etc.

PPS: I didn't quite get where the differences between using controllers+views or components+views/partials are?!

  • Like 2

Share this post


Link to post
Share on other sites
Quote

First I have to say: WOW! I took another look at your module because I got again frustrated with my setup and I wanted to build something on my own. Luckily I remembered your module and came back to the docs before developing something on my own 🙂 

🙇‍♂️

Quote

It would be great to have < prev | next > links on each page at the bottom. I almost missed all the other great pages when reaching the bottom of https://wireframe-framework.com/docs/ (the menu is not visible in the sidebar on such long pages)

Makes sense. I've added this to my todo list, will take care of it soon.

Quote

It would be great to have a simple hello-world walkthrough for setting up a custom wireframe template (instead of providing a full-blown site profile). It's always easier to understand something if you start from scratch than finding your way around several files not knowing which pieces came before and after another.

Again, makes sense. I'm personally not a huge fan of tutorials — it's just not my thing. I prefer to learn by finding a project to work on, or alternatively by digging into existing implementations. Probably explains why there's no tutorial available for Wireframe either.

Another item on my todo list 🙂

Quote

As far as I understood one can define a path for every type that Wireframe is based on (views, controllers, components, etc). And as far as I understood it is possible for components to define custom view files via Wireframe::component('foo')::setView('bar'); Is that correct?

Yes. On a very minor note the API for redefining component view would look like Wireframe::component('foo')->setView('bar'). Also: I typically choose the view in the component, i.e. the constructor picks the most suitable view based on the params it received. Both approaches are fine though, depends on the context! 🙂

Quote

The problem with that approach is that it is not possible to load components from outside of the wireframe folder structure. That's a quite big deal, because if that were possible, we could ship custom components/views/partials (I'm planning on working on styles using RockLESS) directly within our modules and that would just be awesome! [...]

What if we had a new Wireframe type called "package"? This could be included in any Wireframe layout like this:

Interesting idea! I can definitely see value in this, but I'd still like to give it a bit more thought. It's easy to add features, but hard to remove (or significantly alter) them, so I prefer not to rush things 🙂

Out of interest, how do you see this comparing to Markup modules using render method(s)? For an example:

<!-- RockSearch with partial -->
<section><?= $packages->RockSearch->partials->searchform() ?></section>

<!-- SearchEngine -->
<section><?= $modules->SearchEngine->renderForm() ?></section>

One benefit would be that if I wanted to use the default search form as a starting point and start building on top of that, I could just copy the file to local partials directory and change the reference in the layout. This would, obviously, mean no more easy updates, so it's a double edged sword.

In SearchEngine I decided to go with a set of interconnected render methods, each tasked with some specific part of the markup. I felt that this provided the best balance between reusability and customization: one can get pretty far by modifying config settings, but if that isn't enough, it's also possible to make more drastic changes using hooks.

All in all I really like the idea, but it will require a bit more thought and probably some experimenting to strike the perfect balance 🙂

Quote

PPS: I didn't quite get where the differences between using controllers+views or components+views/partials are?!

Hopefully I'm answering the right question:

  • Controllers and views are specific to a single template. Controllers' job is to accept arguments, process data, and pass processed data to the View layer. View — which consists of layout(s), view files, and partials — is there to output said data, so it should be as "dumb" as possible. View doesn't need to know anything about what's going on behind the scenes.
  • Partials were originally just "include files" without the ability to accept params or a dedicated approach for processing data. They were great when you had a relatively static block you wanted to repeat in multiple templates, but if you wanted to pass them params, you'd have to define them in the parent context, etc.
  • Components were added to fill this void. There's always a class that can accept arguments and process data, and usually there's at least one view file meant to render output. (Components can also render output directly by implementing the render() method, or they might not produce any markup at all, so technically component views are optional.)

... and then things changed a bit when partials also got the ability to accept params. Now the biggest difference between components and partials is the class: I prefer not to mix code with markup, which means that if I need a reusable "thing" that needs to, say, fetch data from an API, it's a component. On the other hand if I just need to reuse a block of HTML and perhaps iterate/output some variables in there, most of the time I go with a partial.

In my mind controllers + views (and layout(s)) are the typical way to use Wireframe. Components are handy when you need an element that should be reusable across multiple templates. I guess they're sort of "template-agnostic miniature controller + view" bundles 🙂

Note: how you actually use Wireframe may vary. Your site might not have any controllers, relying on components instead. Or you could produce all the markup in the layout and have no other files (apart from the bootstrap file). What I've described above is just the way I prefer to do things 👌

  • Like 3

Share this post


Link to post
Share on other sites

Hi Teppo,

thx for your thoughts! 🙂

19 hours ago, teppo said:

Out of interest, how do you see this comparing to Markup modules using render method(s)? For an example:


<!-- RockSearch with partial -->
<section><?= $packages->RockSearch->partials->searchform() ?></section>

<!-- SearchEngine -->
<section><?= $modules->SearchEngine->renderForm() ?></section>

Interesting question. I'll try to brainstorm some answers/questions and refer to the two solutions as packages- and modules-route:

  • Modules could only support one markup (how would you ship markup for uikit/bootstrap/tailwind/... ? Or you would need to define renderFormUikit(), renderFormBootstrap().
  • Modules would have markup code in module code (ugly string concats etc) or would need to implement $files->render(...).
  • Related to the previous point: Render files in module-route could have any name, live in any folder, etc - there's no standard, wheras using the packages route a module could have /partials or /views folder and everybody familiar with Wireframe would get what's going on
  • Everybody not familiar would not get what's going on or could maybe even not use it 😐 
  • Maybe it would add more overhead then necessary?! Your modules syntax seems very clear to me!
  • I guess the request came with some background in my mind: I wanted to have packages with PHP (logic + view) code, LESS for style and JS for frontend. For example the searchForm might need some JS to work that you need to add to your theme. But that would also be easy to add to the layout with one single include, so that would also be no real argument for the packages route 🙂 
  • Packages would have the same context as all Wireframe view files (including other partials etc; not sure if that makes sense though, because packages should work as-is (standalone) without any theme related partials...).

I think I have to just try both approaches on my current project and report back my findings 🙂 

19 hours ago, teppo said:

One benefit would be that if I wanted to use the default search form as a starting point and start building on top of that, I could just copy the file to local partials directory and change the reference in the layout. This would, obviously, mean no more easy updates, so it's a double edged sword.

I really want to avoid copying files over to an extra place that breaks updates! 🙂 

  • Like 1

Share this post


Link to post
Share on other sites

Hi @teppo

finally did some more testing, this was my experience:

Ok, it does not stop, but it leads to https://wireframe-framework.com/docs/ -> this is all great informations, but it does not help getting a quick grasp of how to output something on the frontpage...

Please don't get me wrong. Your docs are great, but for me (and maybe others) I think some small improvements can make the experience even greater. Maybe something like this (based on my efforts from today):

---

Here's a rough, unpolished tutorial for getting started with wireframe and getting the concepts on the road:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Default Layout</title>
</head>
<body>
  <h1>Wireframe default layout</h1>
</body>
</html>

Congratulations, that's your first layout! Now add some php variables:

<p>My name is: <?= $view->myName ?></p>

nZ2Bpz7.png

Of course, why should the layout know the name? We need a controller that tells the view what value is in the myName property!

Create /controllers/HomeController.php

<?php namespace Wireframe\Controller;
class HomeController extends \Wireframe\Controller {

  public function myName() {
    return 'Bernhard';
  }
}

XYkOjaR.png

What about my favourite CMS?

<p>My favourite CMS is: <?= $view->cms ?></p>
<?php namespace Wireframe\Controller;
class HomeController extends \Wireframe\Controller {

  public function myName() {
    return 'Bernhard';
  }

  public function cms() {
    return "ProcessWire";
  }
}

ObY797f.png

Ok, I got it - and that's great stuff! 🙂 

 

Now set the alternate template for "basic-page" to "wireframe". Visit a "basic-page" site (eg the 404 page) and you'll see the default layout:

YLvfsj8.png

The variables are empty, because the HomeController is only loaded on the home template and that's for some good reasons. Read about views in the docs: https://wireframe-framework.com/docs/view/views/

Partials to the rescue 🙂 We modify the default layout:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Default Layout</title>
</head>
<body>
  <h1>Wireframe default layout</h1>
  <?php include $partials->name ?>
  <?php include $partials->cms ?>
</body>
</html>

And the partials:

// partials/name.php
<p>My name is <strong>Bernhard</strong>.</p>

// partials/cms.php
<p>My favourite CMS is <strong>ProcessWire</strong>.</p>

Let's make that dynamic!

// name.php
<p>My name is <strong><?= $name ?></strong>.</p>

// cms.php
<p>My favourite CMS is <strong><?= $cms ?></strong>.</p>

// default.php
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Default Layout</title>
</head>
<body>
  <h1>Wireframe default layout</h1>
  <h2>Viewing template "<?= $page->template ?>"</h2>
  <?= $partials->name(['name' => 'Bernhard']) ?>
  <?= $partials->cms(['cms' => 'ProcessWire']) ?>
</body>
</html>

Note that the partials are now echoed instead of included and we added the line that outputs the template of the viewed page!

EXpagcB.pngouH0DL1.png

Note though, that partials are really just for simple chunks of code. At best even static ones. As soon as things get more dynamic, start using components instead of partials: https://wireframe-framework.com/docs/view/components/

What do you think?

  • Like 5

Share this post


Link to post
Share on other sites
1 hour ago, bernhard said:

What do you think?

In the midst of something else right now, so just a quick overall impression: I like where you're going with this!

A tutorial type getting started section has been on my todo-list for a while. Not sure if this is that, or perhaps a "light version", or something? Anyway, I'll give this a bit more thought once I've got some free time, definitely a good starting point 🙂

Edit: just wanted to add that I've been working on a new release for Wireframe. It's going to be a pretty big update, and after that's out I should have some time to revisit the docs.

  • Like 3

Share this post


Link to post
Share on other sites

s26wXJz.png

CYKQjtl.png

Any reason why the resources folder is not created via config?

24 minutes ago, teppo said:

Edit: just wanted to add that I've been working on a new release for Wireframe. It's going to be a pretty big update, and after that's out I should have some time to revisit the docs.

Not sure if that is good news for me 😄 Should I wait for that or will the update be smooth? Thx for the info though 🙂 

Share this post


Link to post
Share on other sites
47 minutes ago, bernhard said:

Any reason why the resources folder is not created via config?

Seems like an oversight from my part, good catch 🙂

47 minutes ago, bernhard said:

Not sure if that is good news for me 😄 Should I wait for that or will the update be smooth? Thx for the info though 🙂 

There shouldn't be any backwards compatibility breaks. At least not intentional ones... 😅

  • Thanks 1

Share this post


Link to post
Share on other sites
15 hours ago, teppo said:

Seems like an oversight from my part, good catch 🙂

Spoke too soon — there was actually a reason for this: "resources" is not a "path" for Wireframe, just an "URL". This may sound weird, but the point is that while Wireframe needs to keep track of code related paths (so that it knows where views, layouts, controllers, etc. are located), it doesn't really need to know where your static resources (CSS, JS, images, fonts, etc.) are.

URLs are added as helpers, so that you can refer from code to your resources directory with <?= $config->urls->resources ?> instead of writing a long directory name over and over again.

Anyway, upcoming 0.12.0 release will provide an option to create directories for specified URLs as well. This obviously only works if they are relative paths (they don't have to be — in some cases I've pointed resources directly to a CDN service, etc.)

  • Like 1

Share this post


Link to post
Share on other sites

Hi @teppo,

could you please give me a quick hint what would be the best way to set a layout for a custom pageclass? I'm creating a page via RockMigrations and it has the PageClass TeaserPage:

class TeaserPage extends Page {
	public function __construct(Template $tpl = null) {
    parent::__construct($tpl);
    $this->_wireframe_layout = 'teaser';
  }
}

This works, but $this->setLayout('teaser') does not. I get an Error Method TeaserPage::setLayout does not exist or is not callable in this context

The docs say that the layout can be defined via hook, but I could not find an example? Thx

Edit: The solution above is not possible: You must assign a template to the page before setting custom field values (_wireframe_layout)

Edit2:

class TeaserPage extends Page {

  public function init() {
    $this->addHookBefore("Wireframe::render", function($event) {
      bd('fired');
      bd($event->page->getLayout(), 'layout');
      $event->page->setLayout('teaser');
      bd($event->page->getLayout(), 'layout');
    });
  }

}

This hook fires but does not change the layout?

GnOGb4J.png

Edit 3: Even this does not work?!

// init Wireframe
$wireframe = $modules->get('Wireframe');
$wireframe->init();

// render the page
if($page->template == GemeindeGuru::tpl_teaser) {
    $page->setLayout('teaser');
}

echo $wireframe->render([
    // 'site_name' => 'Lorem Ipsum',
    // 'lang' => 'en',
    // 'home' => $pages->get(1),
]);

 

Share this post


Link to post
Share on other sites

Hey @bernhard!

That's an interesting question, but sadly one that I don't have an easy answer to. I've never used (read: needed) custom Page classes myself, so not entirely sure of the best approach.

What I can say, though, is that accessing _wireframe_layout property directly is a potentially risky approach: this property is intended for internal use, and there's no guarantee that what you're doing will keep working in future Wireframe versions. If it turns out to be the only viable solution, then maybe this can be rethought, though (now that I'm aware that someone might access it directly, I'll try to keep this in mind that making changes to this may cause issues.)

To answer your question about the method not working, the big question is: at what point does TeaserPage::__construct() execute? This is very likely the issue here; the hooks attached by Wireframe are not available yet.

Note also that Wireframe is an output framework and only intended to run when a Page is being rendered, not when a Page object is being constructed. If you initialize Wireframe in __construct(), you'll likely end up running some Wireframe features in Admin etc. Even if this has no negative consequences, at the very least it's going to result in some unnecessary overhead 🙂

--

What I would currently recommend:

Typically one would set the layout in a) Wireframe bootstrap file (wireframe.php), or b) the Controller class. This would be the easy solution, and also the one that is most in line with how Wireframe is intended to work.

--

Now, if you really wanted to do this in __construct(), you'd have to make sure that Wireframe is initialized first. Assuming that this method runs after the API is ready — which it might not, mind you! — you could perhaps do something like this:

__construct(Template $tpl) {
    parent::__construct($tpl);
    $this->wire('modules')->get('Wireframe')->initOnce();
    $this->setLayout('teaser');
}

... or just access the _wireframe_layout property directly. That way you wouldn't have to (mostly unnecessarily) load Wireframe in Admin etc. Though again, that's not guaranteed to keep working in future versions, and overall I'm not sure that this is the most sensible approach 🙂

--

Going forward, this could be something that needs more attention, though since it's not something I personally use (or see myself using much in the future for that matter) I don't have a whole lot of insight into this whole subject.

1 hour ago, bernhard said:

The docs say that the layout can be defined via hook, but I could not find an example? Thx

I'm not sure which part of the docs you're referring to, but there are many ways to do this. For an example one could hook into Wireframe::render() and alter the layout there. Or hook to Page::render(), or...

There are too many places to list, I'm afraid 🙂

Share this post


Link to post
Share on other sites

Thx for the quick headsup @teppo

2 minutes ago, teppo said:

What I can say, though, is that accessing _wireframe_layout property directly is a potentially risky approach

Yeah, I thought so, that's why I was asking 🙂 

2 minutes ago, teppo said:

To answer your question about the method not working, the big question is: at what point does TeaserPage::__construct() execute? This is very likely the issue here; the hooks attached by Wireframe are not available yet.

Note also that Wireframe is an output framework and only intended to run when a Page is being rendered, not when a Page object is being constructed. If you initialize Wireframe in __construct(), you'll likely end up running some Wireframe features in Admin etc. Even if this has no negative consequences, at the very least it's going to result in some unnecessary overhead 🙂

Yeah, that's what I tried! Please see edit3 in the above post! I've just tried making the page a regular PW page and still it does not work 😞

// init Wireframe
$wireframe = $modules->get('Wireframe');
$wireframe->init();

// render the page
$page->setLayout('teaser');
echo $wireframe->render([
    // 'site_name' => 'Lorem Ipsum',
    // 'lang' => 'en',
    // 'home' => $pages->get(1),
]);

 

Share this post


Link to post
Share on other sites

@bernhard, sorry by the way, looks like your edits were not visible yet when I wrote the answer. Quick answers:

3 hours ago, bernhard said:

Edit: The solution above is not possible: You must assign a template to the page before setting custom field values (_wireframe_layout)

Right. To be honest this falls to the area of which I have no experience whatsoever, but apparently ProcessWire prevents setting any values at this point 🙂

3 hours ago, bernhard said:

Edit2:


class TeaserPage extends Page {

  public function init() {
    $this->addHookBefore("Wireframe::render", function($event) {
      bd('fired');
      bd($event->page->getLayout(), 'layout');
      $event->page->setLayout('teaser');
      bd($event->page->getLayout(), 'layout');
    });
  }

}

This hook fires but does not change the layout?

GnOGb4J.png

When Wireframe::render() is called, Page::render() has already been called earlier, so this has no effect on current render page request. When you're in the wireframe bootstrap file or Controller file and want to change the layout used for current request, you need to go through the $view object:

$this->view->setLayout('teaser');

(In the case of your hook, I believe $event->view->setLayout() should work. Though not sure I've actually tried to do this.)

Page object methods are mostly intended for cases where you want to render some other page, i.e. something like this:

<?php foreach ($page->children as $child): ?>
    <?= $child->setLayout(null)->setView('card')->render() ?>
<?php endforeach; ?>

This is likely something I need to clarify in the docs.

3 hours ago, bernhard said:

Edit 3: Even this does not work?!


// init Wireframe
$wireframe = $modules->get('Wireframe');
$wireframe->init();

// render the page
if($page->template == GemeindeGuru::tpl_teaser) {
    $page->setLayout('teaser');
}

echo $wireframe->render([
    // 'site_name' => 'Lorem Ipsum',
    // 'lang' => 'en',
    // 'home' => $pages->get(1),
]);

 

Same issue here.

  • Thanks 1

Share this post


Link to post
Share on other sites

@bernhard, here's a quick test based on your hook approach:

$wire->addHookBefore('Wireframe::render', function(HookEvent $event) {
	$event->view->setLayout('default');
});

At least from init.php this works as expected. In my own projects I would rather set this in Controller or wireframe.php, though, since this is a little unexpected (and impossible to override without modifying the custom Page class itself, though not sure if that's a problem for your use case.)

  • Thanks 1

Share this post


Link to post
Share on other sites

Thx again @teppo 🙂 

I hope you don't mind if I throw some suggestions here while I'm exploring wireframe 🙂 One more suggestion for the docs:

I think this docs page is a little bloated. Don't get me wrong, I really appreciate the huge amount of informations, but for me it would be easier to start from the minimum working setup and then expand from that. See the docs here: https://wireframe-framework.com/docs/view/components/

It seems to be quite complicated to create components, while all you actually have to do is this:

/components/Card.php

<?php namespace Wireframe\Component;
class Card extends \Wireframe\Component {
}

/components/Card/default.php

I am a Card component!

/layouts/default.php

echo Wireframe::component("Card");

Once I got that via try&error your "advanced" example was great 🙂 

PS: Maybe add a note that /components/Card/default.php could be replaced by adding a render method in /components/Card.php 🙂 

  • Like 3

Share this post


Link to post
Share on other sites
2 minutes ago, bernhard said:

I hope you don't mind if I throw some suggestions here while I'm exploring wireframe 🙂 

Keep 'em coming! 🙂

I'm currently digging into a few Wireframe core things based on your earlier comments. It's really helpful to hear how others are using / getting started with Wireframe, and what sort of troubles they may be running into while doing it. Your comments have already been quite helpful, and I may be able to simplify / streamline some things in the next release.

Docs definitely need some love. I'm still learning how to write useful documentation — one that doesn't omit anything that might prove out to be valuable later on, yet still feels "light enough" to get you up and running quickly. Every time I dive into a new library or language or something like that I seem to run into the very same "just give me the damn facts so that I can try it!" thing. I mean... yes, I get that this is all important and all, but do I really need to know all that just to give it a quick try... 😄

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, teppo said:

@bernhard, here's a quick test based on your hook approach:


$wire->addHookBefore('Wireframe::render', function(HookEvent $event) {
	$event->view->setLayout('default');
});

 

Thx, here is my working version 🙂

class TeaserPage extends Page {

  public function init() {
    $this->addHookBefore("Wireframe::render", function($event) {
      if(!$event->page instanceof self) return;
      $event->view->setLayout('teaser');
    });
  }
}

 

1 hour ago, teppo said:

In my own projects I would rather set this in Controller or wireframe.php

Hm. That confuses me a little. What I'm doing is basically just creating a "coming soon" page. So I'd really just need a template for that. Let's call it "teasertpl". Now to keep things managable in bigger projects I learned that custom pageClasses are a HUGE help. There are always little tweaks we have to do, right? And we all love PW and hooks, right? But in my experience this can quickly get extremely chaotic.

My first approach was to split ready.php into several sections and do this:

// ready.php
include('foo.php'); // all foo related things
include('bar.php'); // all bar related things

The problem with that approach? One day you need to add a hook on init() instead of ready() and all the trouble begins...

Using pageClasses on the other hand is really clean (and also easy):

class MyPage extends Page {

  public function init() {
    // add init hook
  }

  public function ready() {
    // add ready hook
  }

}

I love that approach, because you can even use class constants like MyPage::foofield and you can quickly open that file in VSCode via CTRL+P, type "MyPage", boom --> all there! Want to hook the page edit screen for MyPage pages? Add a buildForm hook on that pageClass! Want a custom random pagename? Add a saveReady hook. Want to auto-publish MyPage pages? One additional line in the saveready hook. 😎

And you get all that by just setting the pageClass in RockMigrations:

$rm->migrate([
  'templates' => [
    'my-page' => ['pageClass' => 'MyPage'],
  ],
]);

Ok sorry, I got a little off-topic, but that was the context 😄  Back to your statement...

1 hour ago, teppo said:

In my own projects I would rather set this in Controller or wireframe.php

Ok, so I have this single "teaser" template and want to render "layouts/teaser.php". I dont have (want/need) a Controller, because that's really just a single page with a logo. Where would you place the setLayout() ? In wireframe.php like this?

$page->view->setLayout('teaser');

This throws an error Call to a member function setLayout() on null

Maybe I'm missing something again? I must admit that it does not feel like I really got the concept yet 😅 

Or would I use a view for something like this that uses the default layout? My idea was to have one default layout for all other templates of this website and one for this "coming soon" page. The 404 page would also be a candidate for a custom layout where I don't need any menus, headers, footers etc.;

Or would you create ONE layout for all that and then show/hide sections based on the page (template) that is currently viewed?

Thx a lot for you help! I'm really looking forward to implementing a streamlined workflow for my projects. Yesterday I had to work on an old project and I got lots of "where did I place this, where did I define that, what the ** did I do here?" moments 😂

  • Thanks 1

Share this post


Link to post
Share on other sites
1 hour ago, bernhard said:

Ok, so I have this single "teaser" template and want to render "layouts/teaser.php". I dont have (want/need) a Controller, because that's really just a single page with a logo. Where would you place the setLayout() ? In wireframe.php like this?


$page->view->setLayout('teaser');

This throws an error Call to a member function setLayout() on null

Maybe I'm missing something again? I must admit that it does not feel like I really got the concept yet 😅 

No worries 🙂

The error is pretty clear: there's no $page->view, but in the context of the wireframe bootstrap file you can access $this->view (or wire('view') / $this->wire('view'), that's the same thing). API variable $view — behind the scenes it's an instance of Wireframe\View, which in turn extends TemplateFile — is basically how you access the View layer (layouts, view files, etc.) from a) the bootstrap file, or b) controller classes.

What I often do is something along these lines:

if ($page->is('template=some|template|or|another')) {
    $this->view->setLayout('teaser');
}

Although if it's just about this one template, then I typically create a controller class that sets this. This keeps things neatly in context — just like you mentioned about custom Page classes 🙂

<?php namespaces Wireframe\Controller;
class TeaserController extends \Wireframe\Controller {
    public function render() {
        $this->view->setLayout('teaser');
    }
}

Note: personally I don't mind having a Controller that is as simple as the one above 🤷‍♂️

Originally Controllers were built to solve more or less the same things that can now often be dealt with custom Page classes, though personally I still prefer the Controller approach. That being said, your idea about handling init/ready hooks is interesting. In a typical project I don't have that many hooks, so I usually just stash them in init.php/ready.php, or perhaps create a module for a specific (bigger) feature (particularly if they apply to more than one template), but this might be worth exploring further in some future project.

(Also a little off-topic, but we've bundled a bunch of "very often needed" hooks into a shared module that we install on all our new projects, and I personally try to keep hooks to minimum — they are powerful, but the more you have the more complex the project gets. Basically the same thing you said about it getting chaotic; I try to fend that chaos off by saying "no" to new features/hooks/etc. as often as possible 😅)

1 hour ago, bernhard said:

Or would I use a view for something like this that uses the default layout? My idea was to have one default layout for all other templates of this website and one for this "coming soon" page. The 404 page would also be a candidate for a custom layout where I don't need any menus, headers, footers etc.;

Or would you create ONE layout for all that and then show/hide sections based on the page (template) that is currently viewed?

This does sound like a good use case for multiple layouts. Again I try to keep my layouts pretty "dumb": in my experience too many bells and whistles and flags and toggles result in unmaintainable code. Everything that is specific to a single template generally belongs to view files instead of layouts; sometimes when I have a single page with a completely custom look — say, a campaign one-pager within a bigger site — I disable layout completely for that page, and put everything in the view (as long as it doesn't get massively complex in itself... in which case components and partials can come in handy) 🙂

2 hours ago, bernhard said:

I'm really looking forward to implementing a streamlined workflow for my projects. Yesterday I had to work on an old project and I got lots of "where did I place this, where did I define that, what the ** did I do here?" moments 😂

 I feel your pain 😛

This was the main motivation for creating the original pw-mvc "framework". Imagine that you've got a team of developers all working on their own projects and inventing their own ways to structure things and solve the same issues. And not just some one-off projects that get handed to a client, but ones that the team has to maintain for many, many years to come.

Proper level of standardization == happiness.

  • Like 2

Share this post


Link to post
Share on other sites

Thx @teppo, greatly appreciated!

30 minutes ago, teppo said:

The error is pretty clear: there's no $page->view, but in the context of the wireframe bootstrap file you can access $this->view (or wire('view') / $this->wire('view'), that's the same thing). API variable $view — behind the scenes it's an instance of Wireframe\View, which in turn extends TemplateFile — is basically how you access the View layer (layouts, view files, etc.) from a) the bootstrap file, or b) controller classes.

Thx, that clarifies it. $page->wire->view does also work 🙂 

30 minutes ago, teppo said:

Originally Controllers were built to solve more or less the same things that can now often be dealt with custom Page classes, though personally I still prefer the Controller approach. That being said, your idea about handling init/ready hooks is interesting. In a typical project I don't have that many hooks, so I usually just stash them in init.php/ready.php, or perhaps create a module for a specific (bigger) feature (particularly if they apply to more than one template), but this might be worth exploring further in some future project.

Well, the great thing about custom pageClasses is that they do also work on the backend. You can use them for hooking the pageedit form for example. Or today I did some custom redirects when a page is save+exit'ed. This really belongs to the page object imho, so it makes sense to have everything in a class extending the Page class. I'm not sure yet where to draw the line between page classes and controllers for wireframe...

30 minutes ago, teppo said:

This was the main motivation for creating the original pw-mvc "framework". Imagine that you've got a team of developers all working on their own projects and inventing their own ways to structure things and solve the same issues. And not just some one-off projects that get handed to a client, but ones that the team has to maintain for many, many years to come.

Proper level of standardization == happiness.

👍

Hm, may I ask for one more advice so that I can continue working on that tomorrow...

My need is quite common, I think:

Templates:

  • home
  • foo
  • bar

Then I want to have 2 layouts:

  • frontpage
    • body sections: header, search, slider, quotes, news, ..., cta, footer
  • default
    • body sections: header, search, main, footer

The links should be:

  • home.php --> frontpage
  • foo.php --> default
  • bar.php --> default

My question is about how you structure such a setup. When using custom "dump" layout files, the <head> for both layouts would be duplicated (same assets, same title, same meta tags etc). If I added a third layout I'd have 3x the same code...

Or do you do something like this?

<head>
  <?= $partials->head->title ?>
  <?= $partials->head->seo ?>
  <?= $partials->head->assets ?>
</head>

I guess placeholders come into play a little later on the road when I start outputting template specific things? Like news-cards with page content of that news-page?

Sorry for all those beginner questions 🙂 

Share this post


Link to post
Share on other sites
17 hours ago, bernhard said:

Well, the great thing about custom pageClasses is that they do also work on the backend. You can use them for hooking the pageedit form for example. Or today I did some custom redirects when a page is save+exit'ed. This really belongs to the page object imho, so it makes sense to have everything in a class extending the Page class. I'm not sure yet where to draw the line between page classes and controllers for wireframe...

The way I see it: if it's output-related, I prefer to put it into a Controller.

This is the layer that handles everything that isn't needed in Admin or when loading the page outside Wireframe render context (hooks etc.) In my "ideal project" Controllers provide the backend implementation for the front-end; an API of sorts. I prefer to keep my view files as "low-logic" as possible, and everything that looks like code (external API calls, database queries, complex loops that iterate over content and convert it into some sort of list, etc.) go to Controllers instead.

Controllers also provide out-of-the-box some caching features that I've found pretty handy: all Controller method return values go into run-time cache (non-persistent, for a single request), and they have built-in support for persistent caching. In the next release Controllers will also interact with the new companion module WireframeAPI. And yes, obviously one of the reasons I personally prefer Controllers over custom Page classes is that I can bundle in features that I find useful in multiple (most) projects 😛

Note that while Controllers are by default tied to a specific Template (TeaserController etc.) that's not a must — especially in the later versions of Wireframe this line gets blurrier (is that a word?) and in some situations a single Controller might be applicable to more than one Template.

All that being said, it's true that the line between custom Page classes and Controllers can be a little blurry, and it's often a matter of preference 🙂

17 hours ago, bernhard said:

Hm, may I ask for one more advice so that I can continue working on that tomorrow...

...

My question is about how you structure such a setup. When using custom "dump" layout files, the <head> for both layouts would be duplicated (same assets, same title, same meta tags etc). If I added a third layout I'd have 3x the same code...

Or do you do something like this?


<head>
  <?= $partials->head->title ?>
  <?= $partials->head->seo ?>
  <?= $partials->head->assets ?>
</head>

I guess placeholders come into play a little later on the road when I start outputting template specific things? Like news-cards with page content of that news-page?

  • For <title> tag and SEO tags (metadata) I use a Markup module. Sadly the module that we use in our projects is not publicly available (yet... @Fokke, what do you think, would you mind releasing it? 😉)
  • Assets often these depend on the layout, so I typically don't bother to convert them to a partial. Unless it's something like a GA or chatbot tag that also includes a snippet of code and an API key or something, those I prefer to split into partials for maintainability.

Now, I'm not entirely sure if I really "got" your question, but there are multiple ways to do this, and I don't think any of them is really "wrong":

  • If you have multiple layouts that share some parts, you could convert those shared parts to partials, components, or even markup modules, depending on the complexity of each of these parts.
  • If it's a one-off thing, i.e. all templates have essentially the same layout except that the Teaser template needs to include JS library X in the head section, sometimes the easy way out is to include a template-specific if statement in a shared layout file. (Emphasis on "sometimes".)
  • If it's something "repeatable", like an array of JavaScript file URLs, you could define it in wireframe.php, Controller class, etc. and then iterate those items and output something like script tags in the layout.
  • ... or you could define a placeholder:
// layouts/default.php
<head>
  <title><?= $page->title ?></title>
  <?= $partials->head->seo ?>
  <?= $placeholders->head ?>
</head>

// views/Teaser/head.php
<meta name="author" content="<?= $page->author ?>">
<script src="my-awesome-library.js"></script>

Placeholders are just a way to "inject" a specific view of a template (or multiple templates) into a predefined location in the layout file. Placeholder slots can also be filled from Controller, though I've found little use for that; sometimes this might be handy if you, say, have a Markup module that should generate the placeholder content, and you already have a controller class. Or something along those lines 🤔

In some relatively simple projects I've had a single layout file, and then placeholders for a) custom stuff in the head area, b) aside content, and c) main content (default view — technically this is a placeholder as well). This works nicely if the page layout is the same across all / most templates, and they just need to inject their own content elements into specific locations 🙂

  • Thanks 1

Share this post


Link to post
Share on other sites

Hi @teppo

Thx, it made click in my head 🙂 I'll write about my journey and come up with suggestions at the end of the post:

First try:

<body>
  <?php
  if($page->is(['template' => [GemeindeGuru::tpl_teaser]])) {
    echo $partials->{GemeindeGuru::tpl_teaser}->body;
  }
  else {
    echo Wireframe::component("Header");
    echo Wireframe::component("Search");
    echo Wireframe::component("Slider");
  }
  ?>
</body>

Nice, this already works 🙂

2nd try:

<body>
  <?php
  switch($page->template->name) {
    case GemeindeGuru::tpl_teaser:
      echo $partials->{GemeindeGuru::tpl_teaser}->body;
    break;
    default:
      echo Wireframe::component("Header");
      echo Wireframe::component("Search");
      echo Wireframe::component("Slider");
    break;
  }
  ?>
</body>

A lot better imho, but what if I wanted to add a custom 404 design? I'd need to duplicate the switch case:

<body>
  <?php
  switch($page->template->name) {
    case GemeindeGuru::tpl_teaser:
      echo $partials->{GemeindeGuru::tpl_teaser}->body;
    break;
    case GemeindeGuru::tpl_404:
      echo $partials->{GemeindeGuru::tpl_404}->body;
    break;
    default:
      echo Wireframe::component("Header");
      echo Wireframe::component("Search");
      echo Wireframe::component("Slider");
    break;
  }
  ?>
</body>

Not bad as well, but I wanted something better 🙂 Next try:

<body>
  <?php
  $body = $partials->{$page->template->name}->body;
  if($body) echo $body;
  else {
    echo Wireframe::component("Header");
    echo Wireframe::component("Search");
    echo Wireframe::component("Slider");
  }
  ?>
</body>
ProcessWire\WireException
Partial file not found: C:/laragon/www/kaumberg/site/templates/partials/home.php

Fast-forward: I did some more testing and realized that I was actually mixing up partials and placeholders! The final (and great) version looks like this:

// layouts/default.php
<body>
  <?php
  if($placeholders->body) echo $placeholders->body;
  else {
    echo Wireframe::component("Header");
    echo Wireframe::component("Search");
    echo Wireframe::component("Slider");
  }
  ?>
</body>

// views/home/body.php
echo Wireframe::component("Header");
echo Wireframe::component("Search");
echo Wireframe::component("Slider");
echo Wireframe::component("News");
echo Wireframe::component("Contact");
echo Wireframe::component("Footer");

// views/teaser/body.php
<div class='uk-height-viewport uk-flex uk-flex-center uk-flex-middle'>
  <div class='uk-text-center'>
    <div><img src='mylogo.svg' class='logo'></div>
    <div class='teaser'><?= $page->title ?></div>
  </div>
</div>

This simply means: If the view file /views/[template]/body.php exists, render it, otherwise render the default sections. That should make it really easy to create custom layouts for some templates whily still having all other assets and scripts in place (like seo, styles, analytics etc). I think I'm happy with this setup 🙂 

I realized, that I was missing the link between $placeholders->foo and /views/[template]/foo.php

I read the docs again and I saw that this information IS already there 🙂 My personal addition to this sentence: If you know where you have to look 😉 

wireframe_layout_view-placeholder-view.1

This illustration makes a lot of sense now. But I didn't get it quickly I have to admit.

Here's my conclusion and suggestion:

IMHO your docs are great explaining "how it works", but they could be improved in regards of "how I can work with it". In my experience this can make a huge difference in the experience of someone looking at it the first time. It's basically what you said earlier:

20 hours ago, teppo said:

Docs definitely need some love. I'm still learning how to write useful documentation — one that doesn't omit anything that might prove out to be valuable later on, yet still feels "light enough" to get you up and running quickly. Every time I dive into a new library or language or something like that I seem to run into the very same "just give me the damn facts so that I can try it!" thing. I mean... yes, I get that this is all important and all, but do I really need to know all that just to give it a quick try... 😄

Exactly that! I think if the docs where split into two sections, the first being "quickstart" and the second being what you already have, that would be brilliant. I know that what I'm asking for is a lot of work, but if we (you 😄) want to build some kind of standard, it should be as easy as possible for everybody to adopt to it 🙂 

Finally I've got a question/request that you have to review from a technical point of view. As stated above, it took some time for me to grasp this easy concept:

$placeholder->default   loads /views/MyTemplate/default.php
$placeholder->head      loads /views/MyTemplate/head.php

If you look at the rest of wireframe this becomes quite obvious:

echo Wireframe::component("Header")        --> /components/Header/default.php
echo $partials->menu->top()                --> /partials/menu/top.php
echo $placeholders->body                   --> /views/[template]/body.php

This really confused me when starting with wireframe! I'd expect placeholders to live in /placeholders or, the other way round, the variable to be called $views

echo Wireframe::component("Header")        --> /components/Header/default.php
echo $partials->menu->top()                --> /partials/menu/top.php
echo $views->body                          --> /views/[template]/body.php

These 3 lines would make it obvious what options you have in your layout file. But maybe that's because I've already a better understanding of what's going on, so maybe other newcomers could comment on this?

What I was really missing was a guide that helps me go through a typical web project: You got a design, you got a PW installation, you installed Wireframe, what then?

Now after 2 days of testing this was the way that worked great for me (so far):

 

  • Set wireframe as alternate template file for all templates that should be handles via wieframe (for this walkthrough we take home.php and basic-page.php of the default site profile).
  • Create a default layout: /layouts/default.php
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title><?= $page->title ?></title>
    </head>
    <body>
      <h1>I am the default layout</h1>
    </body>
    </html>
  • Open the root page of your site and the 404 page --> both should display the default layout

  • We want a common page header and footer that is displayed on all templates. Shared markup with little to none business logic is best placed in a partial.

  • Create 2 files:

    /partials/header.php
    <div style='border: 1px solid blue; padding: 20px;'>I am the page header</div>
    
    /partials/footer.php
    <div style='border: 1px solid red; padding: 20px;'>I am the page footer</div>
  • Now you can output these partials in your layout's body:

    <body>
      <?= $partials->header ?>
      <h1>I am the default layout</h1>
      <?= $partials->footer ?>
    </body>

     

  • What if we wanted a slider on the frontpage? And ONLY on the frontpage? That's what view files are for and they are accessed via $placeholders. (Don't you think that this is really misleading?)
    /views/home/slider.php
    <div style='padding: 20px; border: green;'>I am a SLIDER</div>

    And add it to the layout:

    <body>
      <?= $partials->header ?>
      <?= $placeholders->slider ?>
      <h1>I am the default layout</h1>
      <?= $partials->footer ?>
    </body>
  • Now open both pages on the frontend and you'll see that the slider does only show up on pages having the [home] template. That magic is simply applied via file structure /views/[template]/slider.php
  • Ok now we want to add the current local time of the user visiting the page. That will obviously need some PHP logic and as we want to separate concerns as much as possible we do NOT want to put that code into a partial. Instead, we create a component that consists of business locig (a PHP class extending \Wireframe\Component and a view file that is responsible for the output.
     
    <?php namespace Wireframe\Component;
    class LocalTime extends \Wireframe\Component {
      public function render() {
        return "<div>
          Your timezone: <strong>XXX</strong><br>
          Your local time: <strong>XXX</strong>
          </div>";
      }
    }
    And add it after the footer of our layout:
    <body>
      <?= $partials->header ?>
      <?= $placeholders->slider ?>
      <h1>I am the default layout</h1>
      <?= $partials->footer ?>
      <?= Wireframe::component('LocalTime') ?>
    </body>
    Nice! We get the output at the bottom of all of our pages!
     
  • We know that doing lots of output within php classes is ugly, so we refactor our setup a little and adopt another concept of wireframe: View files for components:
    /components/LocalTime.php
    <?php namespace Wireframe\Component;
    class LocalTime extends \Wireframe\Component {
    }
    
    /components/LocalTime/default.php
    <div>
      Your timezone: <strong>XXX</strong><br>
      Your local time: <strong>XXX</strong>
    </div>

    Of course, this is no a lot better for now, but it is a lot more future proof! Check your frontend, it should still display the same markup for your local time.

  • Now we can add the "business logic" to our component:
     

    <?php namespace Wireframe\Component;
    
    use ProcessWire\WireData;
    use ProcessWire\WireHttp;
    
    class LocalTime extends \Wireframe\Component {
    
      public function __construct() {
        $http = new WireHttp();
        $data = new WireData();
        $data->setArray($http->getJSON("http://worldtimeapi.org/api/ip"));
        $this->timezone = $data->timezone;
        $this->unix = $data->unixtime;
      }
    
    }

    And update our view to output the component properties (note how they magically become available as view variables!):
     

    <div>
      Your timezone: <strong><?= $timezone ?></strong><br>
      Your local time: <strong><?= $unix ?></strong>
    </div>

    Go to your frontend and see the magic!
     

    Your timezone: Europe/Vienna
    Your local time: 1598007959

    What, you don't want a unix timestamp? Ok... let's provide a formatter method so that you can easily output the time in different formats:

    <?php namespace Wireframe\Component;
    
    use ProcessWire\WireData;
    use ProcessWire\WireHttp;
    
    class LocalTime extends \Wireframe\Component {
    
      public function __construct() {
        $http = new WireHttp();
        $data = new WireData();
        $data->setArray($http->getJSON("http://worldtimeapi.org/api/ip"));
        $this->timezone = $data->timezone;
        $this->unix = $data->unixtime;
      }
    
      public function date($format = "d.m.Y H:i:s") {
        return date($format, $this->unix);
      }
    
    }

    @teppo now I got stuck again 😄 How would I display that formatted date in my view file for this component? Or would I need to provide the date format as variable for the constructor ( echo Wireframe::component('LocalTime', ['format' => 'Y-m-d']); )

PS: Regarding the last question. Controllers do expose methods to views. Is there a similar concept for components? I did implement the local time as component because this does not belong to a single template (then it would have been a controller, right?)

  • Like 4

Share this post


Link to post
Share on other sites
On 8/20/2020 at 4:49 PM, bernhard said:

Using pageClasses on the other hand is really clean (and also easy):


class MyPage extends Page {

  public function init() {
    // add init hook
  }

  public function ready() {
    // add ready hook
  }

}

@Zeka wrote me that this snippet does not work for him...

That's because I have a custom pageClass loader in RockMigrations that triggers the init(): https://github.com/BernhardBaumrock/RockMigrations/blob/a034abce7e7fa5436756a2506d9d5f17d8a1b361/RockMigrations.module.php#L1733

So the ready() method in that case would not be triggered still, but you can easily trigger that yourself. the key is that you keep the hooks in the class itself for maintainability!

  • Like 1

Share this post


Link to post
Share on other sites
8 hours ago, bernhard said:

What, you don't want a unix timestamp? Ok... let's provide a formatter method so that you can easily output the time in different formats:

  • @teppo now I got stuck again 😄 How would I display that formatted date in my view file for this component? Or would I need to provide the date format as variable for the constructor ( echo Wireframe::component('LocalTime', ['format' => 'Y-m-d']); )

PS: Regarding the last question. Controllers do expose methods to views. Is there a similar concept for components? I did implement the local time as component because this does not belong to a single template (then it would have been a controller, right?)

Currently the component view file has no way to access the component class. Instead the component class has to pass all the required variables to the view. View can obviously perform actions that seem sensible in that context (such as formatting a date).

have considered adding access to component methods but wasn't entirely sure if it was a good idea. This is still on my todo list and I'll give it more thought in the near future, likely after 0.12.0 has been released. On one hand it might be handy (and in line with how controllers/views behave), but on the other hand every new feature introduced to the module increases complexity, so I want to make sure that it also provides substantial value.

Decisions, decisions 🙂

Your current options would be to a) pass the variable formatted from the controller class to the vie file — and if individual instances of this component could require different formats, then passing the format as a parameter is indeed a sensible approach — or b) handle formatting in the component view file (and again optionally pass the format as a parameter to the component and from there to the view file).

--

I'll have to get back to some of your other points — which were really good, by the way! — later 🙂

  • Like 1

Share this post


Link to post
Share on other sites
11 hours ago, teppo said:

On one hand it might be handy (and in line with how controllers/views behave), but on the other hand every new feature introduced to the module increases complexity, so I want to make sure that it also provides substantial value.

Totally agree and please don't forget to validate all my requests before implementing (as you already do) 🙂 I'm just throwing in ideas and questions as they arise.

What would really be great would be some kind of illustration of the process that one has to go through when adding content to a site (layout). As you said: Decisions, decisions 😄 They should be made as simple as possible 🙂 

Wote4fD.png

built with https://www.yworks.com/products/yed (free and all major platforms); Of course this is totally simplified and missing other decisions that have to be made, like "could it be reused in other projects? --> build a module"

11 hours ago, teppo said:

Currently the component view file has no way to access the component class. Instead the component class has to pass all the required variables to the view. View can obviously perform actions that seem sensible in that context (such as formatting a date).

Hm. One thing that made me really curious about wireframe was this:

Quote

Other public methods are made available as params in Layouts and View Files

If you have a public method called myName() in a Controller class, accessing $this->myName in a layout or view file for related template will refer to the return value of said method.

Thx for the clarification! This feels like a quite big limitation for me. What if I have some piece of code that is needed on several templates (controllers are out) that has some business logic? Should I create a pw module for that? I think that's often too much overhead (you need to install it, define moduleinfo stuff etc). Imho there should be an easy way to handle such situations. Components sound great, but accessing methods should also be easy. I've now tried this:

<?php namespace Wireframe\Component;
class Slider extends \Wireframe\Component {

  public function __construct() {
    $this->component = $this;
  }

  public function foo() {
    return "foo!";
  }
}
<p class='uk-container'>I am a slider! <?= $component->foo() ?></p>
I am a slider! foo!

Ok, that would be a quite easy solution. I'm wondering 2 things though:

  1. Couldn't (shouldn't) that or something like that happen automatically? Just like it's done in controller view files?
  2. Shouldn't Controllers and Components work the same with the only difference that one is applied automatically to the loaded template and the other one has to be loaded manually via Wireframe::component('foo') ? Or are there any other important differences that I'm missing?

---

Thx for all your efforts on this!! 🙂 

11 hours ago, teppo said:

I'll have to get back to some of your other points — which were really good, by the way! — later 🙂

Looking forward 😎

 

  • Like 1

Share this post


Link to post
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.

  • Similar Content

    • By joshua
      As we often use Matomo (former known as Piwik) instead of Google Analytics we wanted to embed Matomo not only in the template code but also via the ProcessWire backend.
      That's why I developed a tiny module for the implementation.
      Right now it's not heavily tested, so I would call it an "alpha" status.
      The module provides the possibility to connect to an existing Matomo installation with the classical site tracking and also via the Matomo Tag Manager.
      If you have also PrivacyWire installed, you can tell MatomoWire to only load the script, if the user has accepted cookies via PrivacyWire.
      To offer an Opt-Out solution you can choose between the simple Opt-Out iFrame, delivered by your Matomo installation, or a button to choose cookies via PrivacyWire.
      You'll find the current (alpha) state in this git repo: https://github.com/blaueQuelle/matomowire
      As soon as it's better tested, I'll add it to the module repo.
      I'm looking forward to hear your feedback!
    • By FireWire
      Hello community!

      I want to share a new module I've been working on that I think could be a big boost for multi-language ProcessWire sites.

      Some background, I was looking for a way for our company website to be efficiently translated as working with human translators was pretty laborious and a lack of updating content created a divergence between languages. I, and several other devs here, have talked about translation integrations and have recognized the power that DeepL has. DeepL is an AI deep learning powered service that delivers translation quality beyond any automated service available. After access to the API was opened up to the US, I built Fluency, a DeepL translation integration for ProcessWire.
      Fluency brings automated translation to every multi-language field in the admin, and also provides a translation tool allowing the user to translate their text to any language without it being inside a template's field. With Fluency you can:
      Translate any plain textarea or text input Translate any CKEditor content (yes, with markup) Translate page names for fully localized URLs on every page Translate your in-template translation function wrapped strings Translate modules DeepL offers translations to the following languages: English (US), English (UK), German, French, Spanish, Portuguese (EU), Portuguese (Brazil, Italian, Dutch, Polish, Russian, Japanese, Chinese (Simplified)
      Installation and usage is completely plug and play. Whether you're building a new multi-language site, need to update a site to multi-language, or simply want to stop manually translating a site and make any language a one-click deal, it could not be easier to do it. Fluency works by having you match the languages configured in ProcessWIre to DeepL's. You can have your site translating to any or all of the languages DeepL translates to in minutes (quite literally).
      Let's break out the screenshots...
      When the default language tab is shown, a message is displayed to let users know that translation is available. Clicking on each tab shows a link that says "Translate from English". Clicking it shows an animated overlay with the word "Translating..." cycling through each language and a light gradient shift. Have a CKEditor field? All good. Fluency will translated it and use DeepL's ability to translate text within HTML tags. CKEditor fields can be translated as easily and accurately as text/textarea fields.

      Repeaters and AJAX created fields also have translation enabled thanks to a JavaScript MutationObserver that searches for multi-language fields and adds translation as they're inserted into the DOM. If there's a multi-language field on the page, it will have translation added.

      Same goes for image description fields. Multi-language SEO friendly images are good to go.

      Creating a new page from one of your templates? Translate your title, and also translate your page name for native language URLs. (Not available for Russian, Chinese, or Japanese languages due to URL limitations). These can be changed in the "Settings" tab for any page as well so whether you're translating new pages or existing pages, you control the URLs everywhere.

      Language configuration pages are no different. Translate the names of your languages and search for both Site Translation Files (including all of your modules)

      Translate all of the static text in your templates as well. Notice that the placeholders are retained. DeepL is pretty good at recognizing and keeping non-translatable strings like that. If it is changed, it's easy to fix manually.

      Fluency adds a "Translate" item to the CMS header. When clicked this opens up a modal with a full translation tool that lets the user translate any language to any language. No need to leave the admin if you need to translate content from a secondary language back to the default ProcessWire language. There is also a button to get the current API usage statistics. DeepL account owners can set billing limitations via character count to control costs. This may help larger sites or sites being retrofitted keep an eye on their usage. This tool is available for all users with the page-edit permission.

      It couldn't be easier to add Fluency to your new or existing website. Simply add your API key and you're shown what languages are currently available for translation from/to as provided by DeepL. This list and all configuration options are taken live from the API so when DeepL releases new languages you can add them to your site without any work. No module updates, just an easy configuration. Just match the language you configured in ProcessWire to the DeepL language you want it to be associated with and you're done. Fluency also allows you to create a list of words/phrases that will not be translated which can prevent items such as brands and company names from being translated when they shouldn't

       
      Limitations:
      No "translate page" - Translating multiple fields can be done by clicking multiple translation links on multiple fields at once but engineering a "one click page translate" is not feasible from a user experience standpoint. The time it takes to translate one field can be a second or two, but cumulatively that may take much longer (CKEditor fields are slower than plain text fields). There may be a workaround in the future but it isn't currently on the roadmap. No "translate site" - Same thing goes for translating an entire website at once. It would be great, but it would be a very intense process and take a very (very) long time. There may be a workaround in the future but it isn't on the roadmap. No current support for Inline CKEditor fields - Handling for CKEditor on-demand hasn't been implemented yet, this is planned for a future release though and can be done. I just forgot about it because I've never really used that feature personally.. Alpha release - This module is in alpha. Releases should be stable and usable, but there may be edge case issues. Test the module thoroughly and please report any bugs via a Gitlab issue on the repository or respond here. Please note that the browser plugin for Grammarly conflicts with Fluency (as it does with many web applications). To address this issue it is recommended that you disable Grammarly when using Fluency, or open the admin to edit pages in a private window where Grammarly may not be loaded. This is an issue that may not have a resolution as creating a workaround may not be possible. If you have insight as to how this may be solved please visit the Gitlab page and file a bugfix ticket.
      Requirements:
      ProcessWire  3.0+ UIKit Admin Theme That's Fluency in a nutshell. A core effort in this module is to create it so that there is nothing DeepL related hard-coded in that would require updating it when DeepL offers new languages. I would like this to be a future-friendly module that doesn't require developer work to keep it up-to-date.
      It's Free
      This is my first real module and I want to give it back to the community as thanks. This is the best CMS I've worked with (thank you Ryan & contributors) and a great community (thank you dear reader). The only cost to use this is a subscription fee for the DeepL Pro API. Find out more and sign up here.
      Download & Feedback
      Download the latest version here
      https://gitlab.com/SkyLundy/fluency-processwire/-/archive/master/fluency-processwire-master.zip
      Gitlab repository:
      https://gitlab.com/SkyLundy/fluency-processwire
      File issues and feature requests here (your feedback and testing is greatly appreciated):
      https://gitlab.com/SkyLundy/fluency-processwire/-/issues
       
      Thank you! ¡Gracias! Ich danke Ihnen! Merci! Obrigado! Grazie! Dank u wel! Dziękuję! Спасибо! ありがとうございます! 谢谢你!

    • By horst
      ---------------------------------------------------------------------------------------------------------------------------------
        when working with PW version 2.6+, please use Pim2, not Pim!
        read more here  on how to change from the older to the newer version in existing sites
      ---------------------------------------------------------------------------------------------------------------------------------
      PageImage Manipulator, API for version 1 & 2

      The Page Image Manipulator is a module that let you in a first place do ImageManipulations with your PageImages. - And in a second place there is the possibility to let it work on any imagefile that exists in your servers filesystem, regardless if it is a 'known PW-image'.

      The Page Image Manipulator is a Toolbox for Users and Moduledevelopers. It is written to be as close to the Core ImageSizer as possible. Besides the GD-filterfunctions it contains resize, crop, canvas, rotate, flip, sharpen, unsharpMask and 3 watermark methods.



      How does it work?

      You can enter the ImageManipulator by calling the method pim2Load(). After that you can chain together how many actions in what ever order you like. If your manipulation is finished, you call pimSave() to write the memory Image into a diskfile. pimSave() returns the PageImage-Object of the new written file so we are able to further use any known PW-image property or method. This way it integrates best into the ProcessWire flow.

      The three examples above put out the same visual result: a grayscale image with a width of 240px. Only the filenames will slightly differ.

      You have to define a name-prefix that you pass with the pimLoad() method. If the file with that prefix already exists, all operations are skipped and only the desired PageImage-Object gets returned by pimSave(). If you want to force recreation of the file, you can pass as second param a boolean true: pim2Load('myPrefix', true).

      You may also want to get rid of all variations at once? Than you can call $pageimage->pim2Load('myPrefix')->removePimVariations()!

      A complete list of all methods and actions are at the end of this post.
       
      You may also visit the post with tips & examples for users and module developers.


      How to Install
      Download the module Place the module files in /site/modules/PageImageManipulator/ In your admin, click Modules > Check for new modules Click "install" for PageImageManipulator Done! There are no configuration settings needed, just install and use it. Download    (version 0.2.0)
      get it from the Modules Directory History of origins

      http://processwire.com/talk/topic/3278-core-imagemanipulation/


      ----------------------------------------------------------------------------------------------------------


      Page Image Manipulator - Methods

      * pimLoad  or  pim2Load, depends on the version you use!

      pimLoad($prefix, $param2=optional, $param3=optional)
      param 1: $prefix - (string) = mandatory! param 2: mixed, $forceRecreation or $options param 3: mixed, $forceRecreation or $options return: pim - (class handle) $options - (array) default is empty, see the next method for a list of valid options! $forceRecreation - (bool) default is false It check if the desired image variation exists, if not or if forceRecreation is set to true, it prepares all settings to get ready for image manipulation
      -------------------------------------------------------------------

      * setOptions

      setOptions(array $options)
      param: $options - (array) default is empty return: pim - (class handle) Takes an array with any number valid options / properties and set them by replacing the class-defaults and / or the global config defaults optionally set in the site/config.php under imageSizerOptions or imageManipulatorOptions.

      valid options are:
      quality = 1 - 100 (integer) upscaling = true | false (boolean) cropping = true | false (boolean) autoRotation =true | false (boolean) sharpening = 'none' | 'soft' | 'medium' | 'strong' (string) bgcolor = (array) css rgb or css rgba, first three values are integer 0-255 and optional 4 value is float 0-1, - default is array(255,255,255,0) thumbnailColorizeCustom = (array) rgb with values for colorize, integer -255 - 255 (this can be used to set a custom color when working together with Thumbnails-Module)
        outputFormat = 'gif' | 'jpg' | 'png' (Attention: outputFormat cannot be specified as global option in $config->imageManipulatorOptions!) set {singleOption} ($value)
      For every valid option there is also a single method that you can call, like setQuality(90), setUpscaling(false), etc.
      -------------------------------------------------------------------

      * pimSave

      pimSave()
      return: PageImage-Object If a new image is hold in memory, it saves the current content into a diskfile, according to the settings of filename, imagetype, targetFilename and outputFormat. Returns a PageImage-Object!
      -------------------------------------------------------------------

      * release

      release()
      return: void (nothing) if you, for what ever reason, first load image into memory but than do not save it, you should call release() to do the dishes! 😉 If you use pimSave() to leave the ImageManipulator, release() is called automatically.
      -------------------------------------------------------------------

      * getOptions

      getOptions()
      return: associative array with all final option values example:
      ["autoRotation"] bool(true) ["upscaling"] bool(false) ["cropping"] bool(true) ["quality"] int(90) ["sharpening"] string(6) "medium" ["targetFilename"] string(96) "/htdocs/site/assets/files/1124/pim_prefix_filename.jpg" ["outputFormat"] string(3) "jpg" get {singleOption} ()
      For every valid option there is also a single method that you can call, like getQuality(), getUpscaling(), etc. See method setOptions for a list of valid options!
      -------------------------------------------------------------------

      * getImageInfo

      getImageInfo()
      return: associative array with useful informations of source imagefile example:
      ["type"] string(3) "jpg" ["imageType"] int(2) ["mimetype"] string(10) "image/jpeg" ["width"] int(500) ["height"] int(331) ["landscape"] bool(true) ["ratio"] float(1.5105740181269) ["bits"] int(8) ["channels"] int(3) ["colspace"] string(9) "DeviceRGB" -------------------------------------------------------------------

      * getPimVariations

      getPimVariations()
      return: array of Pageimages Collect all pimVariations of this Pageimage as a Pageimages array of Pageimage objects. All variations created by the core ImageSizer are not included in the collection.
      -------------------------------------------------------------------

      * removePimVariations

      removePimVariations()
      return: pim - (class handle) Removes all image variations that was created using the PIM, all variations that are created by the core ImageSizer are left untouched!
      -------------------------------------------------------------------
      * width

      width($dst_width, $sharpen_mode=null)
      param: $dst_width - (integer) param: $auto_sharpen - (boolean) default is true was deleted with version 0.0.8, - sorry for breaking compatibility param: $sharpen_mode - (string) possible: 'none' | 'soft' | 'medium' | 'strong', default is 'soft' return: pim - (class handle) Is a call to resize where you prioritize the width, like with pageimage. Additionally, after resizing, an automatic sharpening can be done with one of the three modes.
      -------------------------------------------------------------------

      * height

      height($dst_height, $sharpen_mode=null)
      param: $dst_height - (integer) param: $auto_sharpen - (boolean) default is true was deleted with version 0.0.8, - sorry for breaking compatibility param: $sharpen_mode - (string) possible: 'none' | 'soft' | 'medium' | 'strong', default is 'soft' return: pim - (class handle) Is a call to resize where you prioritize the height, like with pageimage. Additionally, after resizing, an automatic sharpening can be done with one of the three modes.
      -------------------------------------------------------------------

      * resize

      resize($dst_width=0, $dst_height=0, $sharpen_mode=null)
      param: $dst_width - (integer) default is 0 param: $dst_height - (integer) default is 0 param: $auto_sharpen - (boolean) default is true was deleted with version 0.0.8, - sorry for breaking compatibility param: $sharpen_mode - (string) possible: 'none' | 'soft' | 'medium' | 'strong', default is 'soft' return: pim - (class handle) Is a call to resize where you have to set width and / or height, like with pageimage size(). Additionally, after resizing, an automatic sharpening can be done with one of the three modes.
      -------------------------------------------------------------------

      * stepResize

      stepResize($dst_width=0, $dst_height=0)
      param: $dst_width - (integer) default is 0 param: $dst_height - (integer) default is 0 return: pim - (class handle) this performs a resizing but with multiple little steps, each step followed by a soft sharpening. That way you can get better result of sharpened images.
      -------------------------------------------------------------------

      * sharpen

      sharpen($mode='soft')
      param: $mode - (string) possible values 'none' | 'soft'| 'medium'| 'strong' return: pim - (class handle) Applys sharpening to the current memory image. You can call it with one of the three predefined pattern, or you can pass an array with your own pattern.
      -------------------------------------------------------------------

      * unsharpMask

      unsharpMask($amount, $radius, $threshold)
      param: $amount - (integer) 0 - 500, default is 100 param: $radius - (float) 0.1 - 50, default is 0.5 param: $threshold - (integer) 0 - 255, default is 3 return: pim - (class handle) Applys sharpening to the current memory image like the equal named filter in photoshop.
      Credit for the used unsharp mask algorithm goes to Torstein Hønsi who has created the function back in 2003.
      -------------------------------------------------------------------

      * smooth

      smooth($level=127)
      param: $level - (integer) 1 - 255, default is 127 return: pim - (class handle) Smooth is the opposite of sharpen. You can define how strong it should be applied, 1 is low and 255 is strong.
      -------------------------------------------------------------------

      * blur

      blur()
      return: pim - (class handle) Blur is like smooth, but cannot called with a value. It seems to be similar like a result of smooth with a value greater than 200.
      -------------------------------------------------------------------

      * crop

      crop($pos_x, $pos_y, $width, $height)
      param: $pos_x - (integer) start position left param: $pos_y - (integer) start position top param: $width - (integer) horizontal length of desired image part param: $height - (integer) vertical length of desired image part return: pim - (class handle) This method cut out a part of the memory image.
      -------------------------------------------------------------------

      * canvas

      canvas($width, $height, $bgcolor, $position, $padding)
      param: $width = mixed, associative array with options or integer, - mandatory! param: $height = integer, - mandatory if $width is integer! param: $bgcolor = array with rgb or rgba, - default is array(255, 255, 255, 0) param: $position = one out of north, northwest, center, etc, - default is center param: $padding = integer as percent of canvas length, - default is 0 return: pim - (class handle) This method creates a canvas according to the given width and height and position the memory image onto it.
      You can pass an associative options array as the first and only param. With it you have to set width and height and optionally any other valid param. Or you have to set at least width and height as integers.
      Hint: If you want use transparency with rgba and your sourceImage isn't of type PNG, you have to define 'png' as outputFormat with your initially options array or, for example, like this: $image->pimLoad('prefix')->setOutputFormat('png')->canvas(300, 300, array(210,233,238,0.5), 'c', 5)->pimSave()
      -------------------------------------------------------------------

      * flip

      flip($vertical=false)
      param: $vertical - (boolean) default is false return: pim - (class handle) This flips the image horizontal by default. (mirroring)
      If the boolean param is set to true, it flips the image vertical instead.
      -------------------------------------------------------------------

      * rotate

      rotate($degree, $backgroundColor=127)
      param: $degree - (integer) valid is -360 0 360 param: $backgroundColor - (integer) valid is 0 - 255, default is 127 return: pim - (class handle) This rotates the image. Positive values for degree rotates clockwise, negative values counter clockwise. If you use other values than 90, 180, 270, the additional space gets filled with the defined background color.
      -------------------------------------------------------------------

      * brightness

      brightness($level)
      param: $level - (integer) -255 0 255 return: pim - (class handle) You can adjust brightness by defining a value between -255 and +255. Zero lets it unchanged, negative values results in darker images and positive values in lighter images.
      -------------------------------------------------------------------

      * contrast

      contrast($level)
      param: $level - (integer) -255 0 255 return: pim - (class handle) You can adjust contrast by defining a value between -255 and +255. Zero lets it unchanged, negative values results in lesser contrast and positive values in higher contrast.
      -------------------------------------------------------------------

      * grayscale

      grayscale()
      return: pim - (class handle) Turns an image into grayscale. Remove all colors.
      -------------------------------------------------------------------

      * sepia

      sepia()
      return: pim - (class handle) Turns the memory image into a colorized grayscale image with a predefined rgb-color that is known as "sepia".
      -------------------------------------------------------------------

      * colorize

      colorize($anyColor)
      param: $anyColor - (array) like css rgb or css rgba - but with values for rgb -255 - +255,  - value for alpha is float 0 - 1, 0 = transparent  1 = opaque return: pim - (class handle) Here you can adjust each of the RGB colors and optionally the alpha channel. Zero lets the channel unchanged whereas negative values results in lesser / darker parts of that channel and higher values in stronger saturisation of that channel.
      -------------------------------------------------------------------

      * negate

      negate()
      return: pim - (class handle) Turns an image into a "negative".
      -------------------------------------------------------------------

      * pixelate

      pixelate($blockSize=3)
      param: $blockSize - (integer) 1 - ??, default is 3 return: pim - (class handle) This apply the well known PixelLook to the memory image. It is stronger with higher values for blockSize.
      -------------------------------------------------------------------

      * emboss

      emboss()
      return: pim - (class handle) This apply the emboss effect to the memory image.
      -------------------------------------------------------------------

      * edgedetect

      edgedetect()
      return: pim - (class handle) This apply the edge-detect effect to the memory image.
      -------------------------------------------------------------------

      * getMemoryImage

      getMemoryImage()
      return: memoryimage - (GD-Resource) If you want apply something that isn't available with that class, you simply can check out the current memory image and apply your image - voodoo - stuff
      -------------------------------------------------------------------

      * setMemoryImage

      setMemoryImage($memoryImage)
      param: $memoryImage - (GD-Resource) return: pim - (class handle) If you are ready with your own image stuff, you can check in the memory image for further use with the class.
      -------------------------------------------------------------------

      * watermarkLogo

      watermarkLogo($pngAlphaImage, $position='center', $padding=2)
      param: $pngAlphaImage - mixed [systemfilepath or PageImageObject] to/from a PNG with transparency param: $position - (string) is one out of: N, E, S, W, C, NE, SE, SW, NW,
      - or: north, east, south, west, center, northeast, southeast, southwest, northwest
      default is 'center' param: $padding - (integer) 0 - 25, default is 5, padding to the borders in percent of the images length! return: pim - (class handle) You can pass a transparent image with its filename or as a PageImage to the method. If the watermark is bigger than the destination-image, it gets shrinked to fit into the targetimage. If it is a small watermark image you can define the position of it:
      NW - N - NE | | | W - C - E | | | SW - S - SE  
      The easiest and best way I have discovered to apply a big transparency watermark to an image is as follows:
      create a square transparent png image of e.g. 2000 x 2000 px, place your mark into the center with enough (percent) of space to the borders. You can see an example here! The $pngAlphaImage get centered and shrinked to fit into the memory image. No hassle with what width and / or height should I use?, how many space for the borders?, etc.
      -------------------------------------------------------------------

      * watermarkLogoTiled

      watermarkLogoTiled($pngAlphaImage)
      param: $pngAlphaImage - mixed [systemfilepath or PageImageObject] to/from a PNG with transparency return: pim - (class handle) Here you have to pass a tile png with transparency (e.g. something between 150-300 px?) to your bigger images. It got repeated all over the memory image starting at the top left corner.
      -------------------------------------------------------------------

      * watermarkText

      watermarkText($text, $size=10, $position='center', $padding=2, $opacity=50, $trueTypeFont=null)
      param: $text - (string) the text that you want to display on the image param: $size - (integer) 1 - 100, unit = points, good value seems to be around 10 to 15 param: $position - (string) is one out of: N, E, S, W, C, NE, SE, SW, NW,
      - or: north, east, south, west, center, northeast, southeast, southwest, northwest
      default is 'center' param: $padding - (integer) 0 - 25, default is 2, padding to the borders in percent of the images length! param: $opacity- (integer) 1 - 100, default is 50 param: $trueTypeFont - (string) systemfilepath to a TrueTypeFont, default is freesansbold.ttf (is GPL & comes with the module) return: pim - (class handle) Here you can display (dynamic) text with transparency over the memory image. You have to define your text, and optionally size, position, padding, opacity for it. And if you don't like the default font, freesansbold, you have to point to a TrueTypeFont-File of your choice.
      Please have a look to example output: http://processwire.com/talk/topic/4264-release-page-image-manipulator/page-2#entry41989
      -------------------------------------------------------------------





      PageImage Manipulator - Example Output


    • By d'Hinnisdaël
      Format Datetime fields as Carbon instances.
      You can find the latest release and the complete readme on Github.
      Installation
      composer require daun/datetime-carbon-format Usage
      All Datetime fields will now be formatted as Carbon instances instead of strings. Some examples:
      // $page->date is a Datetime field // Output format: j/n/Y echo $page->date; // 20/10/2020 echo $page->date->add('7 days'); // 27/10/2020 echo $page->date->format('l, F j'); // Monday, October 20 echo $page->date->year; // 2020 echo $page->date->diffForHumans(); // 28 minutes ago Frontend only
      The ProcessWire admin seems to expect datetime fields to be strings. This module will only return Carbon instances on frontend page views.
      Date output format
      When casting a Carbon instance to a string (usually when outputting the field in a template), the field's date output format will be respected.
      Links
      GitHub • Readme • Carbon docs
       
       
      PS. I remember reading about a Carbon module in a recent newsletter, but couldn't find it anywhere. Was that you, @bernhard?
×
×
  • Create New...