Skip to content

5. Advanced customization, none of the other choices work

sam detweiler edited this page Dec 17, 2024 · 14 revisions

sometimes none of the the choices seem to work, for example the compliments module
in javascript, the list of compliments is an object '{....}', which is usually fixed in size

compliments: {
	anytime: [..,''..],
	morning: [..,..,..],
	afternoon: [..,..,..],
	evening: [..,..,..],
	"....-01-01": [..,..,..]
}

but in reality the structure is an extendable list, more like an array '[...]', but arrays in json have a different structure
[ fieldname: field_value, fieldname: field_value ]

the compliments object key (anytime, morning...) isn't named..

so, how can we get from one format to the other? a workable format might be an array of objects

{
	"when":"anytime",
	"list": [...,...,...]
}

the schema might look like this

	"compliments": {
		"type": "array",
		"items": {
			"type": "object",
			"properties": {
				"when": {
					"type": "string"
				},
				"list": {
					"type": "array",
					"items": {
						"type": "string"
					}
				}
			}
		}
	},

and the config data like this

	"compliments": [
        {
          "when": "anytime",
          "list": [
            "Hey there sexy!"
          ]
        },
        {
          "when": "morning",
          "list": [
            "Good morning, handsome!",
            "Enjoy your day!",
            "How was your sleep?"
          ]
        },
        {
          "when": "afternoon",
          "list": [
            "Hello, beauty!",
            "You look sexy!",
            "Looking good today!"
          ]
        },
        {
          "when": "evening",
          "list": [
            "Wow, you look hot!",
            "You look nice!",
            "Hi, sexy!"
          ]
        },
        {
          "when": "....-01-01",
          "list": [
            "Happy new year!"
          ]
        }
	]

we can't change the module config format in config.js as we would have to rewrite the module code

we CAN make a custom schema, and just need to convert the config/defaults values to this form format and back to config.js format

enter the converter script in js a new file, named MMM-Config_converter.js , located in the module folder, same location as the schema file

	  // you MUST convert all the multiple module config data items that need converting in this one function call
          // the complete config: object is passed in and the modified version is expected on returned
	  some_function_name: function(config_data, direction){
		if(direction == 'toForm'){
                   // here you would do whatever conversions are required for the data 
		   // in compliments , we need to change the object to an  array 
		   let new_compliments = []
		   Object.keys(config_data.compliments).forEach(when=>{
                                // we have the object key 
				// now we need to create a little 'object' for each element in the array
				// so we will add to the array for each entry in the object
                                new_compliments.push(
					{
						// the schema says the element has a when value (the anytime....)
						"when":when,
						"list":config_data.compliments[when] // and a list value (the stuff to the right of the ':')
					}
				)  
		   })
		   // done processing all the entries in the config format object
		   // now update the passed in config data
		   // we want the data to survive, so cant be local, the JSON library will let us make a copy 
		   config_data.compliments = JSON.parse(JSON.stringify(new_compliments))
		}
		else if direction == 'toConfig'){
                   // we need to go from form format (array), back to expected config.js format object 
		   // setup the empty object
		   let config_compliments = {}
		   // loop thru each array element
		   confg_data.compliments.forEach(element=>{
			   // create a keyed entry in the old format, by using the two parts of the array entry
                           config_compliments[element.when] = element.list
		   })
		   // all done with the array 
		   // save the modified data 
		   config_data.compliments = JSON.parse(JSON.stringify(config__compliments))
		}
		return confg_data // modified
	  }	
	  // this line is critical, we need to tell MMM-Config what the function is
	  // MMM-Config EXPECTS the name to be 'converter', so the export allows you to name your function
	  // any way you like
    exports.converter=some_function_name

see the final version for the compliments module in the MMM-Config/schemas folder

then you can create a custom form section (in the schema.json file (section schema, form, value )) note: compliments supports multiple instances in config.js so THAT is an array too..

here is the config section of the module definition,

 **comments are not allowed in json  but I will put them here for some better explanation**
      "title": "config",
      "items": [
        {
          "type": "array",
          "title": "compliments",
          "deleteCurrent": false,  // if you want the user to delete ANY item in the list, not just the last  set to true (default)
          "draggable":false,       // if you want the user to be able to reorganize the list, set to true (default)
          "items": {
            "type": "fieldset",    // collection of fields with header
            "items": [             // start of list of fields to show in this collection
			                       // the field display will be taken from the schema definition , string, number, .....
              {
                "title": "when to show",  
                "key": "compliments.config.compliments[].when"  // where to get/set the data for this field
              },
              {
                "type": "array",
                "title":"list of phases to show for this time",
                "deleteCurrent": false, // same as above
                "draggable":false,      // same as above
                "htmlClass":"compliments_list",   // in this case I want a custom field class so I can address it in css
                "items": [
                  {
                    "notitle": true,         // dont display any title over this entry
                    "deleteCurrent": false,  // same as above.. altho this might be useful as true, it DOES add a new separate row with the delete button on EACH element
                    "key": "compliments.config.compliments[].list[]"
                  }
                ]
              }
            ]
          }
        },

so the customized compliment format in the form looks like this , with the add/remove buttons for the list of phrases

and at the bottom of this section is another add/remove, for the adding another 'when'

  1. All that is really cool, but,

the custom date format YYY-MM-DD doesn't work properly.. the schema says there is ONE format ....-01-01, but really, thats just one of many. the form handler can't match data (....-04-03 with ....-01-01)

SO.. if we had ANOTHER form field that we could use for the actual data entry, then the drop down selection for date-format or date-time format could expose the other field for data entry, and our conversion script could handle the changes

note that we are using the JSONFORM titlemap feature to make understandable choices, while maintaining the technical settings underneith

so the schema and form sections get some improvements

the schema section

              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "when": {
                    "type": "string",
                    "enum":[
                      "anytime",
                      "morning",
                      "afternoon",
                      "evening",
                      "date-format",
                      "date-time-format",
                      "weather-format"					  
                    ]
                  },
                  "date-format":{
                    "type":"string"
                  },
                  "date-time-format":{
                    "type":"string"
                  },
                  "weather-format":{
                    "type":"string",
                    "enum":[
                      "day_sunny",
                      "day_cloudy",
                      "cloudy",
                      "cloudy_windy",
                      "showers",
                      "rain",
                      "thunderstorm",
                      "snow",
                      "fog",
                      "night_clear",
                      "night_cloudy",
                      "night_showers",
                      "night_rain",
                      "night_thunderstorm",
                      "night_snow",
                      "night_alt_cloudy_windy"
                    ]
                  },				  
                  "list": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }

the form section

          "type": "array",
          "title": "compliments",
          "deleteCurrent": false,
          "draggable":false,
          "items": {
            "type": "fieldset",
            "items": [
              {
                "title": "when to show",
                "key": "compliments.config.compliments[].when",
                "onChange":"(evt,node)=>{let choices=['date-format','date-time-format'];let value=evt.target.value; let i=0; let index=choices.indexOf(value); var parentElement =$(evt.target).closest('fieldset');choices.forEach(f=>{let target=parentElement.find('div[class$='+f+']');let style=(index != i?'none':'block'); target[0].style.display=style;i++})}",
				        "titleMap":{
                      "anytime":"anytime",
                      "morning":"morning",
                      "afternoon":"afternoon",
                      "evening":"evening",
                      "weather-format":"weather",
                      "date-format":"date",
                      "date-time-format":"datetime"
                }
              },
              {
                "key": "compliments.config.compliments[].date-format",
                "title":"date to show",
                "placeholder":"YYYY-MM-DD",
                "fieldHtmlClass":"date-format",
                "type": "text",
                "description": "YYYY-MM-DD, use .(dot) for any value you don't care, for birthday, don't care about year so ....-MM-DD",
                "required":true,
                "onInput":"(evt,node)=>{let value=evt.target.value;if(!date_validator(value)){evt.target.parentElement.classList.add('fieldError')}else {evt.target.parentElement.classList.remove('fieldError')}}"
              },
              {
                "key": "compliments.config.compliments[].date-time-format",
                "title":"date/time to show",
                "placeholder":"min hour day_of_month month dow",
                "type": "text",
                "fieldHtmlClass":"date-time-format",
                "description": "see <a href=\"https://crontab.cronhub.io/\">cron schedule creator</a>",
                "required":true,
                "onInput":"(evt,node)=>{let value=evt.target.value;if(!cron_validator(value)){evt.target.parentElement.classList.add('fieldError')}else {evt.target.parentElement.classList.remove('fieldError')}}"
              },
              {
                "key": "compliments.config.compliments[].weather-format",
                "title":"weather type to show",
                "fieldHtmlClass":"weather-format",
                "required":true
              },

now there is a some extra work to do..