Jump to content

gornycreative

Members
  • Posts

    387
  • Joined

  • Last visited

  • Days Won

    5

Everything posted by gornycreative

  1. Recently updated, alpha channels were rounding to integer rather than a float - now fixed. With the right arrangement, stacked gradients are possible.
  2. I don't use a lot of JS in new stuff, it's the already existing stuff - especially old stuff I've done where I was sloppy and used things without necessarily understanding the ins and outs - a lot of that I have tried to transition into vanilla JS just because jquery was total overkill, but I still run into it often enough. using gen ai is a good idea though. I bet it does a nice job of suggesting alternatives if I want to pop jquery out too.
  3. So it's a funny thing. I started using javascript a lot when I was still doing 'classic' ASP because it performed better than vbscript server-side, and then I shifted over into PHP around the time when Jquery was picking up speed, and then I got back into javascript when jquery was starting to get unpopular (at least here in the US it went through an extended grumble phase) and so I was doing a lot of vanilla js stuff and then with node, react/vue etc... My jquery knowledge is not that great. I know enough to do repairs and some debug but now as I start to work more with client side stuff in PW I need to sharpen up. Is there a faster track than the straight docs anyone would recommend?
  4. This is really helpful for folks who are used to having a single place to review imagery as found in other systems. Thanks!
  5. Can someone please make a documentation page about this file - just a short one - under Docs > Fields, types and input? I feel stupid for having wasted my time solving for this problem, and wasting even more time writing it up and trying to be helpful, when everyone has demonstrated it was entirely unnecessary. There's an entire area dedicated to the PHP API and literally nothing for this.
  6. Okay I've looked all over documentation and asked the web and I can't find anything on .on('reloaded', what it specifically does or how it works. How about a hint? EDIT: Never mind - inputfields.js - got it.
  7. Is there a module that does it this way that I could look at?
  8. Odds are pretty good that you have had to flip this switch: Because you wanted to use a field that uses javascript to stuff some sort of interface feature into the DOM. If you have the setting above flipped to anything but Off you know that the initialization methods of most javascript tools can only pop into place if the basic structure for the field already exists on the page. If the structure isn't there because either a Repeater item hasn't been added, or it hasn't been opened and AJAX is enabled, there is nothing in the DOM for your script to stuff. For the purposes of this conversation, Repeater and Repeater Matrix Inputfields function in the same way and create the same problem. There has been talk of setting up a global set of events and handlers that would provide signals that scripts could listen to. While that conversation continues, I decided I wanted to at least try to put together a design pattern that would allow these kinds of fields to instantiate with repeaters under any circumstance: The field exists on a page edit form without any complications - just the field by itself. The field exists in a repeater template fieldgroup, and that Repeater has no rows yet - they need to get added. The field exists in a repeater template fieldgroup, and the rows exists but they are closed. Case 2 and 3, if you are using the recommended switch setting, do not load the underlying inputfield structure until their state shifts from closed to open. Once they are open, the form fieldset structure loads, but the script calls that you have already made are done - the fields don't work. What sort of design pattern do we need to solve this problem? We need our inputfield/fieldtype module to load the appropriate javascript and css files to bring the methods we need to initialize the filed instances. We need to know if our fieldtype is on a page, and if it will show up in the markup for the Page Edit form. We need to know if there are Repeater fields on a page, if they include our field, and how we can specifically identify them. We need to be able to pass a list of these fields to the admin page on the client side. We need the admin page to initialize the fields it can see, and be ready to initialize fields that it can't see yet. Once these tasks are all accomplished, we will have a JS powered inputfield that works seamlessly whether it is flying solo on a page, or part of a Repeater field structure - whether open at load, closed at load, or brand new. There are parts that both the Fieldtype and Inputfield modules have to play. You will need to use both to get this job done. Most of the heavy lifting is done in the Inputfield portion, but I'll address each file separately as we look at each step. Loading the assets First of all, I'm assuming that you have got your extra module assets for whatever library you are working on in a vendor subfolder beneath your module directory. You could have several packages. Here's how I load assets in InputfieldGrapick: public function renderReady(Inputfield $parent = null, $renderValueMode = false) { // Add JS and CSS dependencies $config = $this->config; $info = $this->getModuleInfo(); $version = $info['version']; $mod_url = $config->urls->{$this}; $mod_scripts = [ $mod_url . "vendor/grapick_0_1_10/dist/grapick.min.js?v=$version", $mod_url . "vendor/spectrum_1_8_1/spectrum.js?v=$version", $mod_url . "{$this}.js?v=$version", ]; $mod_styles = [ $mod_url . "vendor/grapick_0_1_10/dist/grapick.min.css?v=$version", $mod_url . "vendor/spectrum_1_8_1/spectrum.css?v=$version", $mod_url . "{$this}.css?v=$version", ]; foreach($mod_scripts as $ms) { $config->scripts->add($ms); } foreach($mod_styles as $ms) { $config->styles->add($ms); } return parent::renderReady($parent, $renderValueMode); } We get our module and it's current version, add it as a GET parameter to break cache when we update, and add the assets to scripts and styles. Keep in mind that using renderReady() makes sure these scripts load on admin pages where the module's inputfield is loaded, and that they load with other scripts at the head of the document. Now that we've added our vendor files and custom module .js and .css files to the config arrays, it's time to build out the markup for the form. Setting up our target element on the form There are plenty of ways to design your markup for the form. I want to focus on the elements of the __render() method in the InputfieldModule file that are important for what we are trying to do. The main thing we need to get here is the 'name' property of the field instance. Might as well get the page and id as well: public function ___render() { $name = $this->attr('name'); $page = $this->page; $id = $this->attr('id'); ... Once I have started building my fieldset, there is one field I want to set apart as a special field. In the case of Grapick, this is the div that the javascript is going to replace with a gradient color selection UI. $inputfields = new InputfieldFieldset(); $inputfields->label = 'Grapick Gradient'; $f = $this->modules->get('InputfieldMarkup'); $f->id = $name.'-gradient'; $f->label = "Grapick Gradient"; $f->name = $name.'_gradient'; $f->columnWidth = 50; $f->value = "<div class='uk-height-small grapick' id='{$name}_grapick_control'></div><br><div class='uk-dark uk-border-rounded uk-padding-remove'><span class='uk-text-meta' id='{$name}-rule'>{$rule}</span></div>"; $inputfields->add($f); ... There are plenty of other fields on this form, but take a look at the value of this form. I've incorporated the field name into the id for the DIV container. I've done the same for the Rule div and I apply this principle to all of the input id values. In my case, it is always $name followed by a common target. In this case $field_name_grapick_control is the ID that is important. We want to be able to build a list of these IDs and pass them on to Javascript so that we can manipulate them on the frontend when we are editng the form. Bridging the gap between PHP and Javascript In order to bridge the gap between the PHP backend and the Javascript frontend, we use $config->jsConfig(); This function creates a keyed object with nested objects as properties - like a key => value array in PHP. It can be used the same way. You might be tempted to put the call for this here, at the end of the __render() function, and that would work if you didn't have to deal with repeaters. You could create an array such as: $js_array[$name][ 'field' => $name.'_grapick_control', 'loaded' => false, ]; and then add it to a jsConfig key: wire()->config->jsConfig('grapicks', $js_array); and I initially did this. If you only had this single control target on the page, you'd be all set. But with repeaters, every time an instance of the field is loaded it would overwrite your array. You'd end up with only one element on the JS side. Instead we need to move this functionality over to the FieldtypeModule file. Adding our singleton field to the bridge In the FieldtypeModule we will build out a function that provides us with the data we need to get the list of IDs for fields not yet conceived. We start with a hook: public function init() { $this->addHookAfter('ProcessPageEdit::buildFormContent', $this, 'setJsConfig'); } This will provide us with access to the InputfieldWrapper containing the form via $e->form. From here we have the following function (this is what all of the experienced folks really wanted to see): public function setJsConfig($e) { $form = $e->return; $js_array = []; foreach($form->children as $fc) { $f_class = $fc->className; switch($f_class) { case 'InputfieldGrapick' : $ctrl = $fc->name; $js_array[$ctrl]['loaded'] = false; break; case 'InputfieldRepeater' : case 'InputfieldRepeaterMatrix' : foreach($fc->value as $enum => $it) { $fg = $it->template->fieldgroup; foreach($fg as $r_field) { if($r_field->type == 'FieldtypeGrapick') { $ctrl = $r_field->name.'_repeater'.$it->id; $js_array[$ctrl]['loaded'] = false; } } } break; } } wire()->config->jsConfig('grapicks', $js_array); } What this function does is: Grab the form object. Init the js_array variable. Iterate through the children of the form. This provides you with an array of all the inputfields. Run a switch/case against the class name of the field - this gets you to your singleton field right away. Grab the name of the field if it yours, stuff it in the box with a nested array with a single 'loaded' element. In the case of Repeater or Repeater Matrix, we must go deeper. Grabbing the value of each element of the repeater field gives you the template for the element. From the template, you can get the fieldgroup. Iterate through each field in the fieldgroup - again looking for your module InoutfieldModule - but this time as the type of field. For each match, you have enough information to build out what the repeater field name will be when it is loaded from AJAX: your field's name, '_repeater' with the repeater page id. Finally, add that repeater field page id combo into the js_array with a false 'loaded' flag. Once that is done, you now has a $js_array of all the root field names that get used to manufacture the id of your inputfield. If you remember how we added $name in the __render() Inputfields markup for the $name_grapick_control id we assigned to that special div (and the names/ids for the other fields too - you can check out the code) that $name variable will get populated with that $field_name '_repeater' $id combination. We can now access that list on the javascript side. We don't know when the elements with our prized IDs will show up on the page, but we know what to expect when they do. Adding our uncreated repeater babies to the bridge As this point, the basic elements on the PHP side are done. Moving on to the javascript side. I won't go into too many details about the structure of the js file. Suffice to say all of the subroutine functions get stacked up top, then consts and then we get into the core functionality. The strategy here is first to get the array we just sent over the bridge from PHP. In my InoutfieldGrapick.js file, I do that here (skip past the trims and color conversion stuff): /** * Begin grapick implementation */ //globals var upType, unAngle, gp = []; const stopTxt = [], swType = [], swAngle = [], swOrigin = [], swSize = []; const pwConfig = ProcessWire.config.grapicks; console.log('pwConfig',pwConfig); The constant pwConfig is where I stick the array, which is Processwire.config.whatever_key_assigned - in this case 'grapicks' is the property I am grabbing. The console log is an object with a key for each field. I don't want to get bogged down with the details, but here's what we are doing with this list. First, the list has a 'loaded' flag because we need to keep track of what has been loaded from what we know and what has not. The reason for this, is that some fields are going to be available right away on the page, which other will require us to poll the page looking for the element to appear. Once it does, in the case of repeaters, we can initiate the instance and log in the pwConfig object that the field is loaded and ready to go. Once the repeater item is loaded you can collapse it and it doesn't disappear - once it is loaded we are good to go. So I have a pollingRun() method - this assumes everything is loaded until a target is missing: function pollingRun() { let done = true; for (let key in pwConfig) { if (pwConfig.hasOwnProperty(key)) { if(pwConfig[key]['loaded'] == false) { done = false; let ctrl = document.getElementById(key+'_grapick_control'); //console.log(key, pwConfig[key], ctrl); if(ctrl) { createGrapick(key); //This is really whatever function you need to use to instantiate your special field and it passes a modification of the field name to match the ID we set up in our inputfield __render(); } //console.log('Grapick field: ' + key); for (let key2 in pwConfig[key]) { //console.log('---->', key2, pwConfig[key][key2]); } } } } if(done) { //console.log('Done.'); clearInterval(poll); } } At the end of my function that initializes the instance of the javascript activated object - in this case createGrapick() - I set the loaded flag for the given key to 'true': var createGrapick = function(key) { gp[key] = new Grapick({ el: '#' + key + '_grapick_control', colorEl: '<input id="' + key + '_colorpicker"/>', // I'll use this for the custom color picker direction: 'right', min: 0, max: 100, height: '2.5rem', }); ... //Will allow an inputfield to be picked up as changed when using the advanced features of UserActivity. pwStopsWrap.classList.add('InputfieldStateChanged'); build_rule(key); }) gp[key].emit('change'); pwConfig[key]['loaded'] = true; }; Now there is something else I did here, because there is an 'on change' event handler for my particular javascript object - I add an 'InputfieldStateChanged' class to a critical inputfield. The reason that I did this was because I am using the experimental features of UserActivity that track the changes made to a page. If I don't set this extra class on the pwStopsWrap input (which is the main data store for FieldtypeGrapick) the contents of the input will not save because PW uses the existence of this class to detect that a field has changed on the form and needs to be updated when the page is saved. If you aren't using UserActivity with this setting: then the adding of that class doesn't help or hurt you - although you may see warnings that you have unsaved changes left on the page if you mess around with the controls and then try to walk away. At the very end of this function you see I set the pwConfig[key]['loaded'] flag to true. That field is loaded. Great! Now that the pollingRun() function is set, near the bottom of the page I set up an event listener on the DOM because I need to wait until the page is loaded before I do the first run of the pollingRun() function: document.addEventListener('DOMContentLoaded', pollingRun, false); var poll = setInterval(pollingRun, 1000); Once the page had loaded, the static plain version of our field is now active and loaded. If you were to look at the rest of the pwConfig object you'd see that you've got one 'true' and all of the repeater item fields are false, right? Well not always. If you have set your repeater field to remember which items are open, when you load the page you will discover that during the pollingRun() iteration those fields also instantiated. You also see I've set up a 1 second interval call. This runs the pollingRun() function every second, waiting for you to open one of those closed repeater items. Once you do, it detects the target, initializes it and sets the loaded flag for those. Once all of the fields in the pwConfig has true loaded flags, the job is done and the polling interval is stopped. Setting the watchman to catch singletons, hidden babies, revealed babies and unexpected babies At this point nearly all of our cases are covered, but there is one more. What happens when someone adds a new repeater item? There is no way to know in advance with the RepeaterPage id is going to be. So I have another function and another event listener set up: function getGrapickControlNodes() { const nodeSet = document.querySelectorAll('[id*="_grapick_control"]').forEach(item => { keyName = item.id.substring(0,item.id.indexOf('_grapick_control')); pwConfig[keyName] = {'loaded':true}; createGrapick(keyName); }); } This function may seem a little convoluted, but it builds a node set that looks for all of the control elements on the page, and for each one it pulls the root field name (minus the suffix we added - _grapick_control - to identify our javascript instance target) - adds an entry for the newly created repeater item into the pwConfig object, sets the 'loadedl' flag to true and loads the instance. If I didn't add the representative object to the pwConfig object, I'd get a javascript error at the end of the createGrapick() method run because there would be no flag to set. I could have set the flag to 'false' here and then let createGrapick set it to 'true' when it was done. There is no need to run this function very often. Most of the targets on the page have already been populated. But we do want to watch the DOM and see if any new nodes are added: document.addEventListener('DOMContentLoaded', () => { //Look for new grapick fields (to account for repeaters) // select the target node var target = document.getElementById('ProcessPageEdit'); // create an observer instance var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { var nodes = mutation.addedNodes; var node; for(var n = 0; node = nodes[n], n < nodes.length; n++) { test_id = node.id; if(node.tagName == 'LI' && test_id.includes('repeater_item')) { getGrapickControlNodes(); } }; }); }); // configuration of the observer: var config = { attributes: false, childList: true, subtree: true, characterData: false }; // pass in the target node, as well as the observer options observer.observe(target, config); }, false); I added this below our other DomContentLoaded listener. This function sets up a mutation observer to watch the DOM tree starting with the ProcessPageEdit id ancestor. If any nodes are added that are LI elements with a class setup that includes the 'repeater_item' class the getGrapickControlNodes() function will fire. Since both repeater items and repeater matrix items have this class, it will fire if either type of repeater field has an extra item added. So that's it! We set up our special inputfield to incorporate the field name along with a unique suffix. We got a list of all the fields in the ProcessPageEdit Inputfield set that exist on the PHP side. We passed that list via jsConfig() on to the Javascript side, We iterated through that list once the page loads to catch the targets that are already part of the DOM. We watch the page every second for the moment when the user opens an AJAX loading field and make sure those instances are started. We killed off the interval when the list is all loaded. We watch the DOM ProcessPageEdit node and it's descendant with a MutationObserver for any newcomers and instantiate them too. Granted there are many ways to do this I'm sure, but this is what I did with FieldtypeGrapick.
  9. My favorite seems to be aling instead of align.
  10. This fieldtype has been released and is awaiting approval. I changed the angle input to be an integer entry. I did pull together a strategy for building instances within repeaters. I'll do a writeup of the design pattern as a brief tutorial, as I think until we have a proper set of event handlers for repeater actions/AJAX etc. this is a way to build out a field that requires javascript libraries that is repeater compatible. I feel pretty happy with the end result. It is a fun toy to play with if you have never really explored CSS gradients.
  11. I've seen it come up in the past for sure. Worth having a discussion. I mean, even just an on-page array of booleans that state if a field is loaded on the page would be something, but I agree with this: There are classes on page that indicate this for repeaters. InputfieldRepeaterItem InputfieldStateCollapsed <- class gets applied to LI element for closed repeater item InputfieldRepeaterItem InputfieldStateWasCollapsed <- class gets applied to LI once a repeater item is opened. Further, this gets applied once the AJAX content is loaded. The same applies to repeatermatrix - InputfieldRepeaterMatrixItem gets added to the above. For the immediate need I think what I am going to do is: populate a jsConfig array key with field instance names e.g. field_name, repeater_field_name2300 and a 'loaded' flag run an interval that looks for the target field I need to instantiate, looks for the wasCollapsed state and check for the target field. once it is found in the DOM, create the instance and set the field flag to true, and when all the flags are true break out of the interval. It's sloppy, I hate using intervals to poll the page but my hope is that if someone is working with repeaters they are going to want to dig into new items pretty quickly anyway.
  12. For your goals: $files->getCSV() - https://processwire.com/api/ref/wire-file-tools/get-c-s-v/ Images are a little trickier but if you know where they are stored on the server there's just a little post processing required. https://processwire.com/modules/process-page-field-select-creator/ - then populate the child template with fields you need editing As said above, turn off formatting, then you can iterate through using $pages->findRaw() and implode(), etc. https://processwire.com/api/ref/pages/find-raw/ Write to files using $files->putContents(); https://processwire.com/api/ref/wire-file-tools/file-put-contents/ Using a dedicated table breaks one of the features of PW vs WP - every field is a table! https://processwire.com/blog/posts/making-efficient-use-of-fields-in-processwire/ That being said, if you wanted to create a special field that compounded a bunch of related values together, you could look at this module: https://processwire.com/modules/fieldtype-events/ If you are looking for a tutorial that includes creating a custom class to store your value, a more advanced overview is here:
  13. Right, so if it is on the field will not instantiate. Turned off it works fine. The weird thing is that I have something that watches for new elements so that if you have items open that haven't loaded yet and add a new item, the other items will initialize.
  14. I have this working well, it's lined up to release in a little bit. I have run into issues dealing with repeaters because the lack of repeater JS events related to opening and AJAX loading. So there is some limited repeater support until I work out a solution. I will dig in a little deeper after the initial release. Any insight into this issue is welcomed.
  15. Has this problem ever been resolved? Is there an alternative strategy? I have run into this situation with my grapick module. I have a mutation observer watching ProcessPage Edit and initializing fields once nodes are added to the DOM, but it only works in the repeater items that are already open. It would be nice to have some sort of trigger. Alternatively, perhaps I could build a list of fields using my fieldtype, fieldgroups that contain that field, compare it with a list of fields used on a page template, and then I guess watch the repeater/repeater container for new nodes? Oh but that doesn't help because a repeater is a list in a list in a list right?
  16. If an Inputfield requires third party libraries, it makes sense to includes them in the renderReady() method in the Inputfield module file. So for example if you need to include special CSS and/or JS files.
  17. I feel like this is getting very close to a release. The 'Stops' area is the primary data section of the field. If you want to create your own gradient color/stop combos by entering them here you can. AARRGGBB^0-100 is the format with pipes in between. The spectrum color picker is used in the builder. I haven't figured out how to get showIf to work with fields in this context, but the rules will only apply values that make sense with regard to the type of gradient you wish to build. I did include repeating linear and ellipse. I realized at the end I completely overlooked conical, but I know it is getting more use in gradient border button application techniques so I will add it. The repeating method takes your stops, divides them by 100 and multiplies them by the number you put in for size. If you don't put in a number, it defaults to 100px. There are many ways to declare the size of repeating patterns, but this seemed to make the most out of the Grapick format. Everything updates in realtime as you make changes. Might be less intensive ways to do that - I'm looking into it. As a standalone it works, I need to review some of the notes people have made about context and do some testing in repeaters before I release it. I have set up the client-side stuff to accomodate multiple isntances of the builder on a pageedit form, but I'd like to do a little more testing to make sure everything saves properly. I also want to make sure the API works as expected - e.g. if you use the API to set the stops portion can you get the rule out and so on.
  18. I've been trying to get conditional appearance to work for an InputfieldFieldset that is loading as part of the Inputfield render. I'm trying to use name selectors and values from dropdowns but for some reason I can't get things to match. To be clear, this is the form rendered by the Inputfield module when the custom field is included in a page. Perhaps because they are technically subfields of the main field included in the page this can't be done?
  19. FieldtypeGrapick The FieldtypeGrapick module for ProcessWire wraps the Grapick vanilla javascript gradient creation UI and extends the feature set to include settings beyond what the original library allowed for. The original javascript library was written by Artur Arseniev. Aside from requiring ProcessWire 3, the requirements are: PHP >= 7.2 or >= PHP 8.0 This module makes use of the Spectrum colorpicker library. Repeater and RepeaterMatrix items are supported. There is a gremlin in RepeaterPageArray iteration that causes warnings. I've created an issue for it. It does not impact performance. CssGradient Object The FieldtypeGrapick field value is a CssGradient object. $gradient = new CssGradient( $options=[] ); where $options is a list of properties: $options = [ 'style' => 'linear', 'stops' => 'FFFFFFFF^0|FF000000^100', 'angle' => '180', 'origin' => '', 'size' => '', ]; Properties The CssGradient style by default is linear, 180deg, with a white opaque stop at 0% and a black opaque stop at 100%. style $gradient->style: gives you the dropdown value of the style of gradient. Setting this automatically uses the correct settings for the css function and shape parameter as required. Possible values: 'linear' = Linear 'radial-circle' = Radial Circle 'radial-ellipse' = Radial Ellipse 'repeating-linear' = Repeating Linear 'repeating-radial-circle' = Repeating Circle 'repeating-radial-ellipse' = Repeating Ellipse 'conical' = Conical 'repeating-conical' = Repeating Conical Any other value defaults to linear. Depending on the type of gradient selected, origin, angle, and/or size will come into play. The stops are always used to determine the order of colors and their relative locations according to the limitations of each style. origin $gradient->origin: gives you the dropdown value of the origin of the gradient as it applies to radial and conical gradients. The format is X%_Y% if for some reason you want to set a custom X/Y origin. The dropdown values are typically what I find useful for most applications, but I am open to adding other presets. '-100%_-100%' = Far Top Left '50%_-100%' = Far Top Center '200%_-100%' = Far Top Right '-50%_-50%' = Near Top Left '50%_-50%' = Near Top Center '150%_-50%' = Near Top Right 'top_left' = Top Left 'top_center' = Top Center 'top_right' = Top Right '-100%_50%' = Far Middle Left '-50%_50%' = Near Middle Left 'center_left' = Middle Left 'center_center' = Center 'center_right' = Middle Right '150%_50%' = Near Middle Right '200%_50%' = Far Middle Right 'bottom_left' = Bottom Left 'bottom_center' = Bottom Center 'bottom_right' = Bottom Right '-50%_150%' = Near Bottom Left '50%_150%' = Near Bottom Center '150%_150%' = Near Bottom Right '-100%_200%' = Far Bottom Left '50%_200%' = Far Bottom Center '200%_200%' = Far Bottom Right angle $gradient->angle: gives you the angle in degrees of the gradient as it applies to conical and linear gradients. Should be a value between -360 and 360. Measured in degrees. size $gradient->size: gives you the size - of what depends on the type of gradient. For radial ellipse gradients, at applies a size of XX% YY% using the value. So 25 would represent a size of 25% width, 25% height of the container. For repeating linear, conical and radial gradients, the repeating gradient will apply the percentage stops as a percentage of this value. In the case of repeating linear gradients, if you have your stops at 0%, 10%, 50% and 100% and your size is 200, the stops in the calculated rule will be at 0px, 20px, 100px and 200px. For repeating ellipse and conical gradients, a similar calculation is performed, but the units are %, not px. You can get some crazy tartan backgrounds out of this if you stack your gradients up and are creative with transparencies. stops $gradient->stops: gives you the current stop settings in a AARRGGBB^%% format, with each stop separated by a '|' character on a single line. You can role your own gradient ruleset by modiying this property prior to getting the rule. When using the UI you can also reveal the Stops inputfield and change the stops manually, however you will need to save to see the changes in the UI. rule $gradient->rule: gives you the stored rule that is calculated prior to the field value being saved to the database from the UI. Of course, if you are ignoring the UI altogether and just using the class, you will probably ALWAYS want to call getRule() rather than use this property. If you render the field, it will return the rule as a string. Methods The CssGradient has a single public method that is mostly useful when manipulating an instance of the CssGradient class. getRule(string $delimiter) $gradient->getRule(): calculates the rule based on the properties or options you have set for the field. This automatically runs if you have set an $options array and populate the rule property, but if you decide later that you need to change the properties of the object, you'll want to manually call it again to recalculate it. For example, if you programmatically change the stops, you will want to run the getRule() method rather than just grab the rule property. If you pass a string, the first character will be used as an ending delimiter. If you pass an empty string, no ending delimited will appear. By default, the rule is output with a semicolon. Grapick UI The Grapick UI is relatively straightforward. Clicking the (x) handle above a gradient stop removes the stop from the gradient calculation. If you remove all the stops, the bar is transparent. Clicking on the gradient bar sets a stop conveniently set to the color you click on. Clicking on the colorpicker box below the stop line allows you to select the color and transparency of the stop. Click and drag the stop line itself to modify the gradient. Making changes to any of the controls on the field will update the preview and the calculated rule in real-time. You can always cut and paste this rule and use it in your designs elsewhere if you want. Likewise, if you open the Stops inputfield area (which is collapsed by default) you can directly alter the colors and code using a color in an AARRGGBB format and adjust the stop with a number from 0-100. It's fun to play with - experiment with hard and soft lines, size and origin - many interesting effects are possible. Do not forget that the alpha slider is also available. Examples $grk = new CssGradient($options=[ 'style' => 'linear', 'origin' => '', 'angle' => 270, 'stops' => 'FF8345E4^0|FF5A08DB^25|FF2C046B^97|FF000000^100', 'size' => '', ]); echo $grk->getRule(); will give you: linear-gradient(270deg, rgba(131, 69, 228, 1) 0%, rgba(90, 8, 219, 1) 25%, rgba(44, 4, 107, 1) 97%, rgba(0, 0, 0, 1) 100%); while echo $grk->getRule(''); will give you linear-gradient(270deg, rgba(131, 69, 228, 1) 0%, rgba(90, 8, 219, 1) 25%, rgba(44, 4, 107, 1) 97%, rgba(0, 0, 0, 1) 100%) and echo $grk->gerRule(','); will give you linear-gradient(270deg, rgba(131, 69, 228, 1) 0%, rgba(90, 8, 219, 1) 25%, rgba(44, 4, 107, 1) 97%, rgba(0, 0, 0, 1) 100%),
  20. Yeah sure I can do PRs if you want. I ended up forking your migratePageClasses method and cutting out the migration aspect and calling it detectPageClasses. I understand why its pretty redundant for your purposes (primarily page classes in custom modules) but being able to run something to quickly check in directories for new classes without doing a full migration is helpful for my use case.
  21. An issue exists for some users when trying to adjust the thumbnail size slider on image fields if you have the 'hold on fields header to lock' toggle enabled. There also seems to be a delay setting that is undocumented. Is that 700ms for the long hold to lock? Perhaps increases the duration of the long click would help.
  22. I read A LOT of your source code because I have to diff with changes I've made to accommodate my requirements. So the template creation should happen first. Got it. Maybe. The mechanics are usually clear but it sometimes isn't obvious what is triggering what, and in what order. And I know you like "triggering" things ? // object to migrate // usage: $rm->watch("file.php", true, ['trigger'=>$something]); // will trigger $something->migrate() when file.php changes // see RockCommerce.module.php/Product for an example 'trigger' => false, I like the idea of this. Should be fun to build with.
  23. Okay, I found my template to templates typo in the config array. I also forgot that at one point I added in a migratePageClasses method call but subsequently removed it. Putting it back in triggered the migrations in those class files. It seems like once the template name is in the 'magic-templates' cache they are watched/migrated. If I am not putting my classes in a separate module, would I still use migratePageClass? It seems like the preferred pageClassLoader is only for migrating classes that are in a modules file set.
  24. Hey I just saw the new release of 3.34 - and it looks like perhaps there is a cache getting cleared now. I will give it a try and let you know how it goes.
  25. Using RockMigrations 3.33.0 when I copy new custom page class files to the classes directory they aren't getting added to the watchfile list automatically. Previous versions picked up magicpages and added them. Even after I try to add them using watch() and watchPageClasses() for some reason when I output the watchlist they are not appearing. Page classes that already exist are watched - when they are updated the migrations run fine. Is there a watchlist cache I need to clear someplace? None of these classes are part of a module, they are all simply in the classes directory off the root pw folder. e.g. /pw/site/classes/ My page class structure hasn't changed. It's pretty simple. <?php namespace ProcessWire; use RockMigrations\MagicPage; class TestingPage extends Page { use MagicPage; const tpl = 'testing'; const group = 11.55; // I added a check for this constant in the watch() method for instanceof Page so I can have more control over migration order. public function migrate() { $rm = $this->wire->modules->get('RockMigrations'); $rm->migrate([ 'template' => [ 'testing' => [ 'fields' => [ 'title' => [ ], ], 'fieldgroups_id' => 'testing', 'flags' => 0, 'cache_time' => 0, 'sortfield' => 'name', 'useRoles' => 1, 'noParents' => 1, 'childTemplates' => [ 0 => 'test-child', ], 'parentTemplates' => [ ], 'slashUrls' => 1, 'noMove' => 1, 'childNameFormat' => 'title', 'noChangeTemplate' => 1, 'noShortcut' => 0, 'compile' => 3, 'label' => 'Testing', ], ], 'roles' => [ 'guest' => [ 'access' => [ 'testing' => [ 'view', ] ], ], 'copy_editor' => [ 'access' => [ 'testing' => [ 'view', ], ], ], ], 'pages' => [ 'testing' => [ "template" => "testing", "title" => "Testing", "parent" => "name=options,template=options-main", "status" => [ 'on', ], ], ], ]); } } ?> In my install the Options page does already exist.
×
×
  • Create New...