So I'm submitting a feature request patch for the views.get service. I have looked over some threads, such as #654644: views.get method changed in DEV version and #415314: Get filename and filepath from Services views.get, and it seems like this has possibly come up from others before, but in the case of #654644 it seems to have evolved into a discussion regarding displaying a View row as a node.

My request is to be able to setup a View with the "fields" row style and be able to put the rendered values of the fields into the output of the service. From what I've been able to figure out so far, if I use Services to get a View that has a fields row style, the output seems to be a pre-rendered version of the nodes. For example, if I have a field that is supposed to output the URL of a specific imagecache setting, I instead get the fid of the original image.

This patch basically uses each field's theme function to get the content, but without using the format_output option since that will add all sorts of markup I can't use. I also made it use the field's label as the property name. This way, if you have multiple fields in your View that are from the same source, they can have different property names based off of whatever is put in the field's label.

Any thoughts? Is this useful enough to others to add? Patch to follow.

Comments

jonmcl’s picture

The patch....

marcingy’s picture

Project: Services » Services Views
Version: 6.x-2.x-dev » 6.x-1.x-dev

This won't go into 6.x.2 as it is feature frozen moving to new services views project, so as it can be considered for 3.x.

dragonwize’s picture

Status: Active » Needs work

I think the base idea here is great and quite needed. However, there are some areas that need improvement.

  • Use the views field name. Using the label is bad because you are using it as the property of an object and labels can have characters like spaces that object properties can not.
  • You are only returning just the field data without any of the views meta data returned with the views object. While this would be a great option, it definitely needs to be an option instead of being forced one way or another as it is in the module now and in this patch.
  • You are assuming that the field data is node related. The fields row style is used in all views types so this should work and read more generically.
  • This should also cover views computed fields.
kylebrowning’s picture

It be nice to see a newer patch that works. with the suggested changes in #3

apotek’s picture

Trying to write this patch and am having difficulties finding the "official" best way to dynamically handle the fields row rendering. The view, by default, only returns data directly attached to the node, not any of the fields data, nor any of the formatting of those fields that a default views display will give.

Can anyone point me in the direction of the best documentation on this, or which hooks to look at?

apotek’s picture

Use the views field name. Using the label is bad because you are using it as the property of an object and labels can have characters like spaces that object properties can not.

I see the technical point you are trying to make, however, if a creator of a view wants a property named something, then we should respect that name, and not override it because we want to protect the creator from the folly of including non-allowable characters in his or her label (object property). Sticking to the field alias, afaik seems to have its own issues: 1) There will be a host of tickets opened in the ticket queue from users saying "Why doesn't my label show up in the view when downloaded as json?" 2) The field_alias, in my last two weeks of experimentation, have revealed themselves as being wrongheaded too. Consider the field alias of a field that has been set up on the view to have a url to an image linked to via fid.

            $field_label = $field->field_alias;

Result:

[{ 

"node_vid":"http://domain.tld/sites/default/files/imagecache/s/path/to/image.jpg"
}]

In this case, the field_alias is entirely off when it comes to describing in any meaningful way, what is being rendered by the view. Why should this value be called node_vid?

Alternatively, we can use the field id, but this leads to similarly non-sensical results:

"field_image_fid_1":"http://domain.tld/sites/default/files/imagecache/s/path/to/image.jpg",
"field_image_fid":"http://domain.tld/sites/default/files/imagecache/s/path/to/otherimage.jpg",

Given this, I think it's best to use the label requested by the creator of the view. If that leads to problems for that view's usability through services, then the creator of the view needs to adjust the labels accordingly.

Alternatively, we could also use the label, and strip it of any illegal characters.

#################

You are only returning just the field data without any of the views meta data returned with the views object. While this would be a great option, it definitely needs to be an option instead of being forced one way or another as it is in the module now and in this patch.

I think this default is entirely appropriate, and follows the model outlined in this code:


    if ($row_plugin == 'node') {
      $nodes = array();
      foreach ($view->result as $row) {
        $nodes[] = services_node_load(node_load($row->nid));
      }
      $result = $nodes;
    }

No "views metadata" is being served along with the nodes here either, and this was committed code in services_views.

The question here is what it means to retrieve a view. Are we retrieving the result of the view, or the view itself? It looks to me that most use cases would be returning a list of results from a view rather than a construct of a view. I am not even sure how useful the views metadata would be in a service context--other than for export/ingestion into another site that wanted to replicate that view.

##########

You are assuming that the field data is node related. The fields row style is used in all views types so this should work and read more generically.

I'll have to rip apart more than I really want to dare to in services_views_retrieve to do this.

##########

This should also cover views computed fields.

I don't know enough to know what this means. Sorry.

Anyway, here's my patch, which addresses none of your desires, but which addresses the desires expressed by the opener of this thread: "Allow views.get to return rendered fields for 'fields' row style".

I haven't seen any issues opened for views computed fields or returning rendered fields for non-node-related fields. I do see the utility of this, but again, in order to generalize this, it seems to me that the whole of services_views_retrieve would have to be refactored. Let me see if I can put some thought into that.

apotek’s picture

And here it is.

apotek’s picture

Status: Needs work » Needs review
StatusFileSize
new1.07 KB

Renamed $node to $item. This code is really not only being triggered when the results are nodes. It's entirely based on the row_plugin value.

So the remaining item, I guess, is to make this happen also for this case?

  elseif ($return_type == 'view') {
    $view->set_display($display_id);
    $view->execute();
    return $view;
  }
apotek’s picture

Too late in the day. Pls ignore 5 and 6.

raidcha’s picture

Hi,

thanks for this very usefull patche.

the last patch views_service.inc-views.get_fields-1131194-7.patch doesn't work. it returns empty json like: [{},{},{}]
But the patch views_service.inc-views.get_fields-1131194-6.patch works great.

apotek’s picture

Thanks raidcha. Here's the latest working version.

apotek’s picture

You are assuming that the field data is node related. The fields row style is used in all views types so this should work and read more generically.

Is this rewrite what you mean? Will this ensure that the row_plugin gets applied regardless? With the code below, the fields row plugin would be invoked by anything that wasn't a return type of view and whose row style is not 'node'.

  if (!empty($return_type) && $return_type!='view') {
    // We want to keep the result an array.
    $result[] = $view->preview($display_id);
    return $result;
  }

  $view->set_display($display_id);
  $view->execute();

  if ($return_type=='view') {
    return $view;
  }

  $result = $view->result;

  $row_plugin = $view->display_handler->get_option('row_plugin');
  // If row plugin is node, then we should load each node
  if ($row_plugin == 'node') {
    $nodes = array();
    foreach ($view->result as $row) {
      $nodes[] = services_node_load(node_load($row->nid));
    }
    $result = $nodes;
  }
  elseif ($row_plugin == 'fields') {
    $items = array();
    foreach ($view->result as $row) {
      $item = new stdClass();
      foreach ($view->field as $field_id => $field) {
        if (!empty($field->options['label'])) {
          $field_label = strtolower($field->options['label']);
        }
        elseif ($field->field_alias) {
          $field_label = $field->field_alias;
        }
        else {
          $field_label = $field_id;
        }
        $field->pre_render(array($row));
        $item->$field_label = $field->render($row);
      }
      $items[] = $item;
    }
    if ($items) {
      $result = $items;
    }
  }

  return $result;
apotek’s picture

Thinking about this field label thing and how the field id and field alias are not good substitutes for most field-based data.

I could create a dependency on the wonderful transliterate module and strip out bad characters from the field label. But that creates another dependency.

Could also do some ugly regexing:

$label = preg_replace("/\s+/", "_", $label);//replace space with underscore
$label = preg_replace("/[^\w]+/", "", $label);//remove non-word chars (. - etc)
$label = preg_replace("/[^(\x20-\x7F)]*/", "", $label);//remove non-ascii chars.

Part of the issue here is that different object output formats have different needs for their property names. Anything I would write here would end up being a lowest-common-denominator situation (ie, ascii only).

Is there a general purpose function to be called upon that I could use for this, or should I stick to my previous point that if someone defines a label for a field in a view, intending it to be used as a service, then the user has to be knowledgable enough to make sure those labels will work with the object output format served.

Kunani’s picture

apotek I patched this into 7 and it gives me:

Cannot pass parameter 1 by reference in services_views_resource on this line:

$field->pre_render(array($row));

Kunani’s picture

Okay I solved D7 issue by removing the pre_render and render:

$field->pre_render(array($row));
$item->$field_label = $field->render($row);

and replacing with just this THEME Function:

$item->$field_label = $field->theme($row);

Now using FIELDS within views and services 3 I'm getting the output I expected from the very beginning of using this module! I can now control which fields get returned, as well as the labels to match some previous code already accessing these results.

Also previously I had to access CCK custom fields by drilling down through the language UND and VALUE settings, this is no longer needed.

Very happy with this patch and the services module, thanks to all who have contributed.

frazras’s picture

Views Datasources implements this pretty well, they have a display format that allows you to display as JSON or XML, so u cancustomize the fields only for that display. mmy only issue with it was it did not support JSONP out of the box, the issue queue had some tacked up fixes which were not written as a patch or committed to code. JSONP was essential to be able to use the JQuery getJSON function cross domain. Apotek, you probably could review their methodology and rewrite this to model it.

apotek’s picture

Okay I solved D7 issue by removing the pre_render and render:

$field->pre_render(array($row));
$item->$field_label = $field->render($row);

and replacing with just this THEME Function:

$item->$field_label = $field->theme($row);

This is interesting. It's what I tried to do first (use the theme() function), but it didn't work in D6.

It doesn't look like this will ever get committed to D6 services_views, so maybe you should contribute a separate patch to D7 and see if that will get applied. At least D7 will get this functionality.

apotek’s picture

Cannot pass parameter 1 by reference in services_views_resource on this line:

$field->pre_render(array($row));

The solution here would be to define the array as a variable first, and then pass the variable, rather than the array directly.

However, I can confirm that this does NOT work in D6. I am unable to get the fully formed field data using $item->$field_label = $field->theme($row);. The only way I can get the information I need is to use

          $field->pre_render(array($row));
          $item->$field_label = $field->render($row);
apotek’s picture

[deleted duplicate]

kylebrowning’s picture

Category: feature » task
kylebrowning’s picture

Version: 6.x-1.x-dev » 7.x-1.x-dev
Status: Needs review » Patch (to be ported)

#11 has been applied to 6.x, but needs a port for 7.x :/

kylebrowning’s picture

Status: Patch (to be ported) » Fixed

I committed the same version with $item->$field_label = $field->theme($row); instead of

$field->pre_render(array($row));
          $item->$field_label = $field->render($row);

Hope that helps!

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.