rapila / cms-base

The rapila cms’ internals. Please file all issues here if they’re not directly related to a plugin or the sample site.
http://www.rapi.la
3 stars 1 forks source link

sort order should survive js/json #237

Closed juergmessmer closed 6 years ago

juergmessmer commented 6 years ago

It's an unpleasant experience that select options are not ordered the way they are prepared on the server side. Let's discuss if we could change this behavior without too much problems.

I should know, but I forgot, i tried to work around with StdClass objects, but I didn't achieve the desired result. I guess this concerns php and population the selects in js

sabberworm commented 6 years ago

I think we already support passing arrays to populate(), no?

juergmessmer commented 6 years ago

in widget.js populate() ? Yes, as far as the options are delivered/processed as key/value, but that doesn't effect the sort order, or am I mistaken?

sabberworm commented 6 years ago

So that’s what we need to do: serialize options as arrays, not objects.

juergmessmer commented 6 years ago

How to? a current example, how would I change this?

public function getLinkOptions($sLinkCategoryId, $sSortField) {
    $oQuery = VideoPlayerFrontendModule::getLinksQuery($sLinkCategoryId, $sSortField);
    $oQuery->select(array('Id', 'Name'));
    return $oQuery->find()->toKeyValue('Id', 'Name');
}
juergmessmer commented 6 years ago

The query already includes the order...

juergmessmer commented 6 years ago

Don't understand "serialize options as arrays, not objects", sorry :-) if I serialize the array of key values I get something like:

a:3:{i:26;s:31:"Back to the Middle (India Arie)";i:24;s:25:"Mr. Magic (Amy Winehouse)";i:25;s:32:"Never too much (Luther Vandross)";}

This is obviously the wrong serialized response, since js throws an TypeError: cannot use 'in' operator to search for 'length'

So what's a boy to do?

sabberworm commented 6 years ago

a:3:{i:26;s:31:"Back to the Middle (India Arie)";i:24;s:25:"Mr. Magic (Amy Winehouse)";i:25;s:32:"Never too much (Luther Vandross)";}

This is PHP serialization, not JSON serialization (which is what I meant).

It seems $oQuery->find()->toKeyValue('Id', 'Name') serializes to the following JSON:

{
  "26": "Back to the Middle (India Arie)",
  "24": "Mr. Magic (Amy Winehouse)",
  "25": "Never too much (Luther Vandross)"
}

(which is an object in JSON parlance).

What you need is an array:

[
  {"key": 26, "value": "Back to the Middle (India Arie)"},
  {"key": 24, "value": "Mr. Magic (Amy Winehouse)"},
  {"key": 25, "value": "Never too much (Luther Vandross)"}
]
sabberworm commented 6 years ago

In PHP, the following would work:

json_encode([["key" => 26, "value" => "Back to the Middle (India Arie)"]]);

or

$oValue = new stdClass();
$oValue->key = 26;
$oValue->value = "Back to the Middle (India Arie)";

var_dump(json_encode([$oValue]));

To get from the current to the desired state, use the following (I’m sure we have such a function already. Otherwise, add it to ArrayUtil.):

function transformOrderedKVForPopulate($aKV) {
    $aOrdered = [];
    foreach($aKV as $mKey => $sValue) {
        $oValue = new stdClass();
        $oValue->key = $mKey;
        $oValue->value = $sValue;
        $aOrdered[] = $oValue;
    }
    return $aOrdered;
}

Test:

$aKV = unserialize('a:3:{i:26;s:31:"Back to the Middle (India Arie)";i:24;s:25:"Mr. Magic (Amy Winehouse)";i:25;s:32:"Never too much (Luther Vandross)";}');

$aOrdered = transformOrderedKVForPopulate($aKV);

print json_encode($aOrdered, JSON_PRETTY_PRINT);

prints

[
    {
        "key": 26,
        "value": "Back to the Middle (India Arie)"
    },
    {
        "key": 24,
        "value": "Mr. Magic (Amy Winehouse)"
    },
    {
        "key": 25,
        "value": "Never too much (Luther Vandross)"
    }
]

as expected.

juergmessmer commented 6 years ago

I should have figured it out. I'm just too overwhelmed by...

Thanks a lot for your patience and the enlightening explanations!

I try to implement it in other places, the richtext/ckeditor might be a challenge :-)

juergmessmer commented 6 years ago

Actually I didn't serialize or json_encode the whole thing, just returned it from php like this:

    public function getLinkOptions($sLinkCategoryId, $sSortField) {
        $oQuery = VideoPlayerFrontendModule::getLinksQuery($sLinkCategoryId, $sSortField);
        $oQuery->select(array('Id', 'Name'));
        $aResult = array();
        foreach($oQuery->find() as $i => $aLink) {
            $aResult[$i]['key'] = $aLink['Id'];
            $aResult[$i]['value'] = $aLink['Name'];
        }
        return $aResult;
    }

and it work perfectly: is there a need to make stdClass objects, is it safer, or better code?

sabberworm commented 6 years ago

Yes, WidgetJSONFileModule does the serialization. If you read carefully you’ll notice I never said you needed to do that I only said: it’s what happens.

A stdClass is better because it always serializes to an object. For arrays, there are complicated rules for when they become JSON objects vs. when they become JSON arrays.

juergmessmer commented 6 years ago

Thanks, it looks simpler and cleaner too :-) (my memory...)

    public function getLinkOptions($sLinkCategoryId, $sSortField) {
        $oQuery = VideoPlayerFrontendModule::getLinksQuery($sLinkCategoryId, $sSortField);
        $oQuery->select(array('Id', 'Name'));
        return WidgetJsonFileModule::jsonOrderedObject($oQuery->find()->toKeyValue('Id', 'Name'));
    }
juergmessmer commented 6 years ago

I fixed most missing input order issues.