Planet Drupal

Subscribe to Planet Drupal feed
Drupal.org - aggregated feeds in category Planet Drupal
Updated: 41 min 35 sec ago

Pixelite: Adding Apple and Android favicons to Drupal

Sat, 13/12/2014 - 01:00

As you end up building more and more websites that target mobile devices (e.g. iPhone, iPad, Android, Windows), you need to supply an ever increasing amount of favicons. This process can be complex if done by hand, luckily there is an easy way to introduce these into your Drupal site.

What you will need

Before we start you will need a high quality icon to begin with, the icon should be:

  • 260x260px (i.e. square)
  • a PNG with transparency as needed
  • recognizable when shrunk right done to your browser favicon (so don’t use your entire logo complete with words).
Generating the favicons

This is where the really handy realfavicongenerator.net website comes into play. I have used many other websites that offer similar functionality, but this seems to be the best, and is dead simple to use.

You will need to upload the 260x260px PNG file, and also select a hex color for the Windows 8 tile, but this should be straight forward.

I also opt for the option “I will place favicon files (favicon.ico, apple-touch-icon.png, etc.) at the root of my web site.” as this seems the most sensible place for them anyway.

When you complete the process, you will be able to download a zip file containing a whole bunch of icons and XML files, this is fine, extract them to your docroot for Drupal.

Adding the favicons to Drupal

You now will need to edit your html.tpl.php inside your theme, and add the code that the generator provides. The code should resemble something like this:

1 <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png"> 2 <link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png"> 3 <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png"> 4 <link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png"> 5 <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png"> 6 <link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png"> 7 <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png"> 8 <link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png"> 9 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png"> 10 <link rel="icon" type="image/png" href="/favicon-192x192.png" sizes="192x192"> 11 <link rel="icon" type="image/png" href="/favicon-160x160.png" sizes="160x160"> 12 <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96"> 13 <link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16"> 14 <link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"> 15 <meta name="msapplication-TileColor" content="#b91d47"> 16 <meta name="msapplication-TileImage" content="/mstile-144x144.png">

You will notice though that Drupal likes to place it’s default favicon into the <head> section of the page, we need to remove this in order for it not to mess up the above code you inserted.

<link rel="shortcut icon" href="http://[YOURSITE]/misc/favicon.ico" type="image/vnd.microsoft.icon" />

The following code below can be inserted into your template.php file for your theme to remove the default favicon from Drupal:

1 <?php 2 /** 3 * Remove the unneeded favicon from the head section. 4 */ 5 function YOURTHEME_html_head_alter(&$head_elements) { 6 foreach ($head_elements as $key => $element) { 7 if (!empty($element['#attributes'])) { 8 if (array_key_exists('href', $element['#attributes'])) { 9 if (strpos($element['#attributes']['href'], 'misc/favicon.ico') > 0) { 10 unset($head_elements[$key]); 11 } 12 } 13 } 14 } 15 } 16 ?>

There you have it all done.

Extra for experts - Google’s theme-color meta tag

Google recently announced that from Chrome 39 onwards on Android Lollipop (5.0+), a new meta tag will be supported

<meta name="theme-color" content="#b91d47" />

This is what your site’s title bar now looks like (instead of boring and grey).

This meta tag can be added to your html.tpl.php file as above.

Comments

Let me know if this has helped you, and also if you have any other tips and tricks when it comes to favicons on your mobile devices.

Categories: Elsewhere

Mediacurrent: Protect thyself! Don&#039;t send TESTING emails to REAL users

Fri, 12/12/2014 - 22:05

Have you ever accidentally triggered emails to real users while working in a development environment? Or how about accidentally pushed data to a "live" third party service from a development environment?

Categories: Elsewhere

CiviCRM Blog: Drupal Views in CiviCRM Dashlets

Fri, 12/12/2014 - 19:58

Here at Skvare, we strive to make Drupal and CiviCRM work as one to accomplish goals in a way that is simple and intuitive. Continuing our work in Drupal/CiviCRM integrations, we’ve cooked something new up for you all. We would now like to take this opportunity to introduce Views in Dashlets.

What is Views in Dashlets?

Views in Dashlets is a Drupal module that allows one to create a dashlet containing a Drupal View. That is right, in addition to CiviCRM reports you can use the power of Drupal Views to create a customizable experience. This opens grand new opportunities to use our imagination and drive to strengthen the bond between Drupal and CiviCRM. A majority of the functionality of Views is currently at your fingertips, with further enhancements on the horizon.

How did we get to this point?

We had the idea of rendering a View in a dashlet, but that’s all it was. An idea. We researched extensively trying to find out how exactly a dashlet works. Curiously, I created a forum post where I received a small piece of knowledge and took our first big step in creating this module. After that, it was a matter of development of ideas. Thank you totten for the quick and helpful response.

Basic Instructions

It is very simple to create a dashlet. All you have to do is:

  1. Open/create your view and add a new “CiviCRM Dashlet” display
  2. Configure the display to your liking and save
  3. Visit www.yourdomain.com/civicrm and click “Configure Dashboard”
  4. Add your created dashlet to a column and click “Done”

And now your brand new dashlet is on your dashboard!

Why Views in Dashlets?
  • Simplicity -- Using CiviCRM reports to create dashlets is a great feature of CiviCRM, but new reports require PHP and SQL coding skills. Some organizations have staff with these skills, but many do not. Many more people do have the ability to use site-building techniques to create Views through its powerful UI.
  • Customize - Customize your view to render your CiviCRM or Drupal data just how YOU want it. Quickly add sorts, filters, fields, relationships, no results behaviors, and rewrite field output functionality.
  • Style - Make it pretty! Use View’s built in style features to wrap fields in html elements and add classes to fields for css styling.
  • Content -- More than just data, place content on the dashboard. Display a node, add links to documentation, or list your latest Drupal Commerce orders. If it can be Viewed, it can be on the Dashboard.
  • Combine - Use in combination with the CiviCRM Entity and Entity Reference modules to create rich data structure displays combining Drupal content with CiviCRM data. Also, there are tons of great modules that expand Views functionality and most will work with Views in Dashlets.
  • Reuse - Use the same view for a Drupal page and CiviCRM dashlet.
  • Much more that we have not thought of!
"What can I do to help?"

If you would like to join the journey of Views in Dashlets then go ahead and create an issue. This issue could be a bug report or even a feature request. Views in Dashlets is a work in progress, but there is ALOT you can do with it already. We would be very grateful for your feedback. This is a team effort, and our community is the number one team.

For more information, visit the project page at https://www.drupal.org/sandbox/brandonferrell/2389543

Categories: Elsewhere

Drupal Watchdog: Migrate API

Fri, 12/12/2014 - 19:29
Article

The migrate API works with plugins and stores the configuration for those plugins in a configuration entity. There are a number of plugin types offered: source, process, and destination are the most important. Source merely provides an iterator and identifiers, and most of the time the destination plugins provided by core are adequate, so this article will focus on process plugins.

Process plugins

Nothing gets into the destination unless it is specified under the top level process key in the configuration entity. Each key under process is a destination property and the value of it is a process pipeline. Each “stage” of this pipeline is a plugin which receives the output of the previous stage as input, does some transformation on it, and produces the new value of the pipeline.

There are a few plugins which indeed only use the pipeline value as input – for example, the machine name plugin transliterates the input (presumably a human name) and replaces non-alphanumeric characters with underscores. However, if that was all plugins could do they wouldn’t be too useful. Instead, every plugin receives the whole row and the name of the destination property currently being created.

Each stage in the process pipeline is described by an array, where the plugin key is mandatory and the rest is just the plugin configuration. For example:

process: vid: - plugin: machine_name source: name - plugin: dedupe_entity entity_type: taxonomy_vocabulary field: vid

The above mentioned machine name transformation is run on name and then the entity deduplication plugin adds a numeric postfix ensuring the vid field of the taxonomy_vocabulary entity is unique. That is the canonical format of the process pipeline.

Categories: Elsewhere

Lullabot: Coding in Schools

Fri, 12/12/2014 - 17:49

In this episode, Amber Matz and her guests Eric Schneider and Matthew Tift talk about the successes and challenges on how parents and school officials worked together to get coding into the curriculum in Minnetonka Schools. Eric Schneider is the Assistant Superintendent for Instruction, Minnetonka Public Schools and Matthew Tift is a Senior Developer at Lullabot and a Minnetonka parent.

Categories: Elsewhere

ThinkShout: Oregon Zoo Small Actions

Fri, 12/12/2014 - 17:00
Oregon Zoo: Small Actions

The Oregon Zoo in Portland approached us to develop an action portal component for their Drupal web site. The action portal is a tool that suggests real-world actions that anyone can take to help wildlife survive and flourish. A social sharing component is important for spreading these tips organically. Pun intended. Like wildflowers.

Many sites integrate social sharing, but there are a couple of things that make the Zoo's action portal different. The main difference is that by sharing an action, you are saying that you've actually done that action in the real world, and you are encouraging your friends and followers to take the same action. The aim is not just to generate site traffic, but rather to encourage people to make real change that has a tangible impact on wildlife. Also, the shared content is more personalized, since it's a combination of a single species and the action that you've taken, plus custom messaging the visitor would like to add.

The original intent was to enable visitors to share an action on several social channels: Facebook, Twitter, etc. During technical planning, it was decided that Facebook alone would be the best place to start. We would integrate directly with Facebook and track the shares internally with a custom integration code interacting with Facebook's API.

When we began implementation, we spent a little more time exploring options for sharing on multiple channels, compared to Facebook only. There would be a couple of benefits of sharing directly on Facebook. Using their API would pave the way for deeper integration in the future, taking advantage of Open Graph properties as a starting point. We would have better control over messaging, and we would have complete control over how logging happens in Drupal. And I must say, the Facebook developer documentation is top notch.

But adding the ability later to share on other social networks would require additional API integration for each site. We wanted to consider paving a clearer path forward, so we looked into existing services for sharing on multiple sites. There are many: Gigya, AddThis, ShareThis, and more. For something to work for us, it would need to be free or very inexpensive, allow us to customize the shared message, and provide some statistics, mainly for a share count to display on the site. The ShareThis service ended up working best for us. When using any of these services, there is less control over how shares are logged.

We presented the client with these options along with the pros & cons of each and, ultimately, it was decided that we'd use ShareThis. Having approximate share counts was an acceptable tradeoff in exchange for the benefit of being able to share to multiple social networks.

So, back to how we actually did this...

Structurally, we started with two content types: Action (for the action we want people to take) and Animal (Species that relate to the actions). These each have mostly common field types, such as image and body text.

On the Action content type, we added an Animals entityreference field in order to make the connection between the two content types.

There are three new pages for this feature: the main landing page, the animal detail page, and the action detail page. We created an Animals view for the landing page and action detail page, and we created an Actions view also for the Explore by Action tab of the landing page and for the animal detail page. For the tabs on the landing page, we created a simple block using hook_block_info() and hook_block_view().

Something that's easy to miss when initially planning lists of things is how sorting should be controlled. Since an action references multiple animals, we use that order for displaying animals on the action detail page. But we were pretty limited in how to control the order of actions on an animal detail page. We needed independent sorting control between animals on action pages, and actions on animal pages. We opted to stay with the native drag and drop sorting of entityreference fields, so we added a matching entityreference field on animals to reference actions, and added the Corresponding Entity References to keep these references in sync with each other. Now we have native draggable sorting on both content types. There are several other methods that could have been used, such as adding a weight field, using the draggable views module, or using nodequeue, but using CER with a pair of entityreference fields kept complexity at a minimum.

An essential goal of this feature is sharing an action. The requirement was to have the sharing widget appear on individual actions only when listed on an animal detail page. The shared message is a combination of elements from both content types: the image and name of the animal, plus the contents of a Sharing Message text field from the action. The the URL shared is related to the action.

Message when sharing the FSC action from the Chimpanzee page:

Here's how we put that together. We start by including the global stuff for the ShareThis widget. An implementation of hook_views_pre_render() adds some javascript settings and includes the ShareThis javascript library. To add the unique things to each action, we add a new variable "sharethis_attributes" in hook_preprocess_views_view_field(). This variable contains a string of pseudo attributes: st_url="http://example.com/the-page" st_title="Example Page Title" st_image="http://example.com/image.jpg" st_summary="This is the text that will be shared." st_via="OregonZoo". We use that variable in a very specifically-named template file that takes effect for only this field in this view. The rest of the markup and classes placed in that field template came from ShareThis.

<?php print $output; ?> <div class="sharethis-custom"> <span class='st_sharethis_vcount' displayText='ShareThis' <?php print $sharethis_attributes; ?>></span> </div>

All of this work: content types, fields, image styles for the image fields, views, and the handful of custom hook implementations are bundled together in a new custom feature.

Check out the small actions pages at the Oregon Zoo site and see if there is a small action you can take that will have an impact on a wild animal you care about. There are some great tips that will help you live cleaner and sustain our irreplaceable wildlife.

Categories: Elsewhere

Stanford Web Services Blog: Adaptive Architecture: Leave Room to Evolve

Fri, 12/12/2014 - 15:55

All forward-thinking technologies share one attribute: the original designers intentionally build in opportunities for future users to innovate. It requires humility and a belief in the creativity of others. This is true for buildings, computers, networks, and other tools.

Categories: Elsewhere

Gábor Hojtsy: The Drupal 8 configuration schema cheat sheet

Fri, 12/12/2014 - 11:20

After over a month of concentrated work, Drupal 8 was ready today to finally flip the switch and enforce strict configuration schema adherence in all TestBase derived tests in core. See the announcement in the core group.

If you are a Drupal 8 contrib developer and provided some configuration schema earlier (or you integrate with an existing core system like blocks, views, fields, etc.) then your tests may now fail with configuration schema errors. Unless of course all your configuration schema is correct: #highfive for you then.

Otherwise I thought you'll have questions. There is of course the existing configuration schema documentation that I helped wrote. However if you are a visual person and want to get an understanding of the basics fast, I thought a cheat sheet would be a great tool. So sat down today and produced this one in the hopes it will help you all! Enjoy!

Categories: Elsewhere

Blair Wadman: Programmatically assign roles to users in Drupal

Fri, 12/12/2014 - 09:24

This post is part of a series of posts on making changes in Drupal programmatically rather than in the Drupal interface.

In this tutorial, we will be programmatically assigning role(s) to user(s). You would typically do this in a site deployment module in order to automate this task rather than having to manually assign the roles in the Drupal UI.

Categories: Elsewhere

Bluespark Labs: 10 Challenges You Face when Designing for Locations

Fri, 12/12/2014 - 06:00

Designing a website for an organization with multiple locations is challenging. Especially when those locations have their own needs, goals, and identities. Libraries, in all shapes and sizes, face many challenges when building or redesigning their website. They need to build something that students and patrons can use for research as well as something that actually helps people interact with the library’s various locations. This becomes especially difficult when the library is spread across several buildings and departments — and the people that work at the different libraries often have very different ideas about how their library should be represented on the Web.

Building a great library website is an ambitious project with the ultimate goal of serving the students, faculty, staff, and community.

On the surface, building a library website might feel like just another web project, but when you dig into it, you see there are many, many unique challenges stemming from the unique relationship the library has with it’s virtual and physical spaces. In this article, I explore 10 of those challenges and some possible solutions. Like any web project, though, every situation is unique. I prefer to focus on guidelines and considerations rather than describe actual solutions.

Challenge #1 - Who is our primary audience and what is their context of use?

What’s interesting about Universities—and Libraries fall victim to this, too—there are plenty of audiences to go around and each of their needs must be met for the website to be considered a success. Unfortunately, having too many audiences is like having too many cooks—you end up with something bland that no one likes.

Once you know who your primary audience is, you can explore their Contexts of Use. Contexts of Use are the place, time, and situation in which they will be using the website.

  • When will they need help finding a location?

  • What in the space are they looking for? (A building? Equipment? Something else?)

  • Where are they when they need to find that location?

Sidenote: When we talk about audiences, we also often talk about their goals. Multiple audiences might have similar goals. (How many audiences for a University Library have “Conduct independent research” as a primary goal?) Contexts of Use, though, differentiate these goals across the audiences. A student who needs to conduct independent research at the library website will approach it differently than a faculty member.

So why are contexts of use important? Well, they reveal things like why people are searching for a location (meeting a group, or maybe they need a copy machine, or maybe they want a quiet place to study) and allow you to design experiences for those contexts.

Challenge #2 - Are we a library? Or do we have libraries?

University Libraries are spread all over campus. There’s almost always “The Library” — the one building that everyone thinks about when you tell them to meet you at “The Library” but that is probably just one location of many.

Not to mention the many affiliated libraries—the independent collections managed by departments—the literature collection tucked away in the basement of the Humanities building. How are these related to the library and does your audience understand that relationship? Often this is a question much bigger than a website design project—venturing into your overall strategy, but the answer (and its trickle down effects) can hinder or help your audience accomplish its goals.

And all that leads to the question: Are you a University Library—or are you the University Libraries? It’s an identity question that affects your brand, the representation of that brand online, and the freedom you give the individual libraries.

Challenge #3 - Libraries are not Buildings

Repeat after me: Libraries are not buildings. For some of your libraries, this is painfully obvious. A special collection might be tucked into the corner of a building—be considered its own department, have its own hours and staff. That library is located within a building.

For other libraries, though, it’s less obvious. For example, at the UCLA Library, the Powell Library is located in the Powell Library building, but so are several special collections. You see the confusion? In the physical world, libraries have a special connection to the information they hold—that is the only place you can access that information. It goes without saying that, in the virtual world, these boundaries no longer exist. But because our understanding of physicality (that, and the trend of naming a building after its current use) lead to a tendency to conflate the actual library with the building that houses it.

Where it becomes painfully obvious is when a library is spread across multiple buildings—something that can often happen in space-starved Universities. (And let’s be honest, no matter how large the University, there is always a need for more space.)

There are several key instances when confusing the building with the library will actually create project over-runs. Buildings need to be managed separately from the libraries. The buildings, like the libraries themselves, may have unique names and will definitely have physical locations.

Even more challenging, though, is that this misunderstanding can also lead to poor decision-making about how to structure content. Just like the books and journals of a library live within the physical walls of building, some library staff might think their content should live within the virtual walls of their particularly library’s sitelet. This is where having well-defined persona with prioritized goals and contexts of use become critical to the success of the project. You can use these to explore whether people are first finding a specific library before beginning their search OR are they expecting to search for relevant research from a centralized research section OR something else entirely. (And, of course, stir in a healthy dose of usability testing to verify your hypothesis.)

Challenge #4 - Finding the right location

Here’s a fun one.

How do you know people are getting the right location?

It’s easy when you have a library called The Humanities Library and people are searching for the Humanities Library. But what happens when you have The John Doe Library of the Humanities and people refer to it as the Hum. Or some people call it the Hum and others call it the Humanities Library. It can get confusing real quick if you don’t have an option to add nicknames to your libraries (add nicknames to your buildings while you’re at it, too).

Challenge #5- When the library doesn’t matter, but the location does...

Finding a location is pretty easy when people are searching for a specific location—whether that be a library or a building. But there are plenty of contexts in which a user needs something that is location-agnostic.

Maybe they need a copier or a printer or a computer. Most libraries will have copiers spread all over campus—and some with different pricing. Keeping track of all the amenities that each location offers can be no easy task, but can go a long way to helping your audience actually get what they need from the library.

Tip: Think beyond equipment and things like wifi. Do some libraries offer study rooms? Or some places might offer a group study area where you don’t need to keep your voice down. These are all important amenities that people will be looking for. Taking note of the questions your students are asking about your locations will give you insight into the types of amenities you should be offering and how you should be categorizing your various locations.

Challenge # 6 - Is it open?

One of the biggest challenges seems like it would be the simplest. Students (and all of your audiences, really) need to know one of two things: 1) Is it open now? 2) Will it be open when I need it?

Of course, answering these questions is tricky. You’ve got your normal hours, your holiday hours, your end-of-the-semester hours, your summer hours—so many special circumstances, how do manage them all, much less present them to the user in an easy to read format? There’s really no simple answer other than to keep it as simple as possible and to conduct usability testing on sketches and prototypes with real users.

One of the useful, yet tricky, things we did with UCLA was to add “Open Now” as a search facet, so that all results returned only buildings that were open now—particularly useful for the students that needed a late night copy machine (context of use!) as well as including an “Open Now” indicator right in the location pages’ navigation bar.

Challenge #7 - Destinations within a location

“Is it open?” is not always an easy question to answer. Often, you will have destinations within a location that have separate hours than the main location. While the library will be open, the computer room might not be.

The solution we implemented for UCLA Library was to allow each location to have Destinations with their own descriptions, contact information, and hours. A future iteration of this should also include walking directions from various entrances—assuming that those entrances are clearly designated.

Challenge #8 - What’s going on at the Library?

Libraries host some great events that need to be listed on the website. There are, though, two challenges you face with events.

The first part of this challenge comes from conflating the building and the library as illustrated by this story:

Once upon a time, there was a librarian that worked at the Library of Biological Sciences. She took it upon herself to organize a lecture series from visiting scientists. The first event’s popularity took her by surprise; she had reserved the largest room in her library’s space, but it was still standing room only. So, for the second event, she opted to host the event at the Humanities Library who had a space twice the size of her largest space.

Challenge part 1: If the Library of Biological Sciences is hosting an event at the Humanities Library space, does the event display on the Library of Biological Sciences sitelet?

Challenge part 2: The Library of Biological Sciences is hosting an event at the Humanities Library space, should that event display on the Humanities Library sitelet?

And the answer (drumroll, please): it’s complicated. Ideally, it displays in both areas. Both libraries have an interest in promoting the event (and it should be the same content—not two different entries), though they have different reasons for doing so. The Library of Biological Sciences wants to promote an event they have spent a lot of time and resources organizing while the Humanities Library wants to let people know what’s going on in their space.

The real answer: It should be up to the sitelet moderators to determine what events they promote. They should have strong guidelines generated from Engagement and Content strategies, but the final decision comes from the autonomy you give the libraries themselves. (More on that to come.)

In the above challenge, the Library of Biological Sciences is hosting an event related to Biology in the Humanities Library space. It’s pretty clear cut as to why the two might want to promote the event. But what if the Humanities Library were to invite the author of a crime thriller that paid exquisite attention to the forensic details of the case—something that many people from the Library of Biological Sciences might be interested in? Should the event display on the Library of Biological Sciences sitelet?

The answer is not a simple one. Probably, yes, it should. Again, though, it should be up to the sitelet moderator who would be following Engagement and Content strategies.

Challenge #9 - Balancing autonomy and control

How unique are your locations? From the space they contain to the style of signage and posters used throughout the library. Maybe some of the smaller libraries don’t have a unique brand while the larger ones do. Or, perhaps, your library system has a style guide that all of your staff rigorously apply in everything they create.

Ideally you find a nice balance between command-and-control and complete anarchy. You want each location to feel part of the same family without taking away the things that make them feel unique. There are several ways to do this, including allowing each location to:

  • control the type of content they include on their homepage.

  • customize some of the visual styles on their page -- perhaps background image and other areas that can affect the basic feel of the site.

  • control the layout of their homepage.

You want to give your staff enough autonomy that they can ensure their library sitelet best represents the unique branding and style they have crafted in their space.

Challenge #10 - Consistency between the virtual and the physical

On the flip side of giving your staff autonomy, we have the need to give your audiences a consistent experience across locations and on the website. At its simplest, this means that you display the same information in the same way across sites. An event should look the same across all sites.

It goes deeper than this, though, even into the branding and labeling across your entire Library system, which is really a much larger project than the website redesign. But if you think about it (and I know librarians have!), a key part of finding your way through the physical location is the signage. As much as possible, you should create consistency between the signage in your spaces -- meaning that all libraries are consistent in the labeling and iconography.

Designing location search becomes so much easier when all libraries refer to the place with all the computers as the same thing. Keep labels the same across the different locations. If you call it a “Computer Lab” at one location, resist the urge to call it the “Computer carrels” at another location. (This might mean some overall discussions within the libraries—nothing like a joint project to surface all the inconsistencies that might be confusing your audience.)

Controlled Vocabularies are your friend—both in labels and in iconography. Further, this consistency also helps your audience understand what’s available at a location (as presented on the location pages and search results) and even navigate to those destinations within the space.

Take the UCLA Library as an example. The UCLA Library used some custom made icons to designate various destinations in their libraries, creating consistency across the various locations, the main website, and the location sitelets. Once we started creating the Destinations for each location, we realized that not all Destinations had iconography, so our creative director spent some time expanding on their visual vocabulary. It was critical that everything used on the website feel like it could also be dropped onto library signage without appearing out of place.

Here you can see some of their signage and how iconography plays an important role in conveying what’s available at the destination.

Both the locations and destinations on the website use these same icons to communicate the available amenities.

Sidenote: Consistency can be useful when it’s helpful. But it can also get in the way of communicating important differences—whether those differences come from the brand or facility. Divergence can be a good thing. You just need to ensure that the divergence is meaningful and useful for your audience. Perhaps there is a difference in a Computer Lab (a place where students can use computers) and Computer Carrels (a place designed for laptop use -- no computers provided). However, subtle differences may not be important to people and calling attention to them may only serve to confuse them more.

Bonus Challenge - Who owns what

One last, very important aspect of any web project: who is responsible for editing and maintaining the content on the website. Even if you have a copywriter or two, you are still faced with the gargantuan task of maintaining a mountain of information. (And sadly, this is no exaggeration.) No matter your process for editing and creating content, you need to identify the owner of each content and what their responsibilities entail. (Are they editing the content as needed? Are they filing change requests to the copywriter?)

In Conclusion...

Reconciling the needs of these varied and diverse stakeholders, libraries, locations, and library users can be a daunting task.  Hopefully, if you take the time to address the challenges in this series your end result will meet the needs of the most important audience—as well as many of the needs of your secondary and tertiary audiences.  With such a large network of stakeholders and users, you won’t please everyone, but with good user research and usability testing, you’ll get a lot closer to that goal, and you’ll have a firm foundation for the choices you make.

Tags: Drupal Planet
Categories: Elsewhere

Propeople Blog: Drupal UX Improvements: When Node Forms are a Nuisance

Fri, 12/12/2014 - 00:16

Node forms can be big. With field collections, reference fields, tags, taxonomies, location fields, and others, node forms can actually be really big. This can make an editor’s experience so frustrating that it’s a surprise she doesn’t have a heart attack when she sees how much data she needs to enter in order to save the form.

Sound familiar? Well, in this article we will share some tips and tricks that we use to simplify editors’ lives and to make the Drupal editing experience more user-friendly.

Split a form into tabs

If you have too many fields on one screen, it’s nearly impossible for an editor to remember what he entered in the beginning by the time he’s reached the end of the page. The answer to this quandary is simple: it’s time to split up the form.

It is possible to use different criteria to split forms into more manageable sections, or tabs. The criteria you’ll use will depend on the type of form and its intended purpose. Forms can be split up by field types, required vs. optional fields, priority rank, more vs. less frequently used; the list goes on. To create an even better user experience, ask the editors themselves about how they’d like forms to be organized. If they have been using a particular form for a while, it’s likely they’ll have plenty of valuable feedback regarding the best way to make the forms more intuitive and improve the efficiency of their workflow.

From a technical perspective, there are multiple ways to achieve more manageable forms. The one we use most frequently utilizes vertical tabs. An example of this is shown in the screenshot below.

 

Vertical tabs help editors to concentrate on one thing at a time and improves the navigation experience when working with especially large forms. A good rule of thumb is to divide the form into chunks that won’t require users to scroll through more than 1 to 1.5 pages to complete all the elements on any given section.

In addition to vertical tabs, you have the option to set up horizontal ones. This can easily be achieved with the Field Group module.

Split a form into columns

Some editors set up their workstations to use fairly wide screens. In this case, another practical approach is to have multiple columns. This solution can be used to nicely group fields together so they can be viewed side by side.

Drupal's Panels module make it easy to configure forms that use columns. Simply set up the layout for a node’s edit form and arrange the fields however you like.

Warn editors about unsaved changes

If you use JIRA or Google Docs, you’ve probably seen a warning message like the one shown below.

 

For a busy editor juggling multiple tasks, this friendly little pop-up can be a lifesaver. And of course… there’s a module for that.

Allow us to introduce Node Edit Protection. It helps editors to remember to save their changes before navigating away from a form. This becomes especially handy if you’ve split your forms into multiple tabs, as editors may think that simply switching to another tab automatically saves their changes.

Taxonomy tag widgets

You won’t hear any complaints from us about the standard autocomplete feature and the way it enables editors to quickly select appropriate tags. There are multiple widgets, however, that put the icing on the autocomplete cake. One of these that we particularly like is Chosen.


 

Another good one is Autocomplete Deluxe.

 

Links

Sometimes editors need some help building links in WYSIWYG or when using link fields. This is especially true when editors need to search for and quickly locate the content or URL they want to reference. Enter the Linkit module, which acts as a link-specific autocomplete function.

 

With the Linkit Picker module, we can even have a custom view to search for the content we would like to link to. This allows us to configure additional filters to help editors find content more effectively.

 

References

If you are using reference fields, there is a nifty module called References Dialog. This module allows editors to create a referenced entity while working within a node using a dialog box.

 

This comes in very handy, especially when your referenced entities include only a handful of fields and can fit comfortably into the dialog pop-up.

May we suggest...

For more great ideas that will have editors singing your praises, check out this presentation from DrupalCon Amsterdam 2014 about building a better backend, as well as our colleague Boyan Borisov’s presentation about improving Drupal's editorial experience.

Interested in learning even more about how Propeople can create a Drupal platform with user-friendly backend functionality for you? Don't hesitate to contact us.

Tags: DrupalUXcontent managementService category: TechnologyCheck this option to include this post in Planet Drupal aggregator: planetTopics: Tech & Development
Categories: Elsewhere

Drupal core announcements: All TestBase derived tests now enforce strict configuration schema adherence by default

Thu, 11/12/2014 - 22:03

Configuration schema was originally introduced to help describe configuration for translations. Then it expanded considerably and is now used to export configuration entities automatically for example (unless you want to write code to manually define what to export). Configuration schema is also used to automatically typecast values to their expected types. This ensures that although PHP and web forms in general favour strings over all other types, the right types are used when saving configuration. That is important so when deploying configuration, only actual changes will show up in the difference, no random type changes. Schema enforces certain rules and best practices of configuration on its users, for example that each piece in the active configuration should have an owner. Finally configuration schema based configuration validation helps find several types of bugs in code that is otherwise not or incorrectly tested.

Therefore after a month+ of work to make all core tests pass strict configuration schema checking, we are making TestBase default to strictly check all configuration against configuration schemas. There are only a few tests exempt from this in the testing of the configuration system itself. This affects all contributed module developers writing TestBase (WebTestBase, KernelTestBase, etc.) extending tests. This may result in new test failures which indicate either issues in your schema, your configuration, your tests, migrations, etc. Either way it indicates that in some cases unexpected data structures are generated.

Read more in the change notice.

Categories: Elsewhere

Mediacurrent: Upcoming Webinar: Migrating The Weather Channel Onto Drupal

Thu, 11/12/2014 - 17:39

The Weather Channel (TWC) has one of the most trafficked websites in the world, which makes it one of the largest Drupal sites in the world.

Categories: Elsewhere

Blink Reaction: Creating Custom Search Pages with Search404 and Apache Solr Search

Thu, 11/12/2014 - 17:19

Imagine somewhere deep in your site that you have a page with the alias:

/how-to-build-drupal-site.

Now, let’s imagine this page is not a link in your site’s menu. Your visitor remembers that they have seen this page on your site and starts typing in the address line:

Categories: Elsewhere

Acquia: 5 PHP Components every Drupal 8 Developer should know: Part 2 -- Guzzle

Thu, 11/12/2014 - 16:15

In our previous blog post, we took a look at Composer, a PHP-based class autoloader and dependency management tool, and used it to begin managing some dependencies and downloaded Guzzle. Guzzle is a PHP-based HTTP client library that greatly simplifies the process of consuming RESTful web services. In this blog, we’re going to explore some of Guzzle’s basic abilities, and use it to begin building a simple SDK of sorts.

Categories: Elsewhere

Code Karate: Drupal 7 Sweaver Module: Change your theme style with no CSS code

Thu, 11/12/2014 - 14:49
Episode Number: 185

The Drupal 7 Sweaver module makes it easy to change the style of your Drupal theme without having to write any CSS code or dig through any template files. The Sweaver module provides a simple to use designer toolbar that sits at the bottom of the page and allows you to instantly change the look of your Drupal theme.

Tags: DrupalDrupal 7Theme DevelopmentDrupal PlanetUI/DesignCSS
Categories: Elsewhere

Code Karate: Smart Paging: How to display a node on multiple pages

Thu, 11/12/2014 - 14:18
Episode Number: 184

The Smart Paging module is one of those "nice to have" modules. This module allows the ability to break content on a particular node into multiple pages. It is important to remember though that this doesn't mean you have to create multiple nodes or Drupal pages. This module works off of the same node. Neat!

Tags: DrupalContent TypesDrupal 7Site BuildingDrupal Planet
Categories: Elsewhere

Metal Toad: Drupal 8 Migrations, part 4: Migrating Nodes from Drupal 7

Thu, 11/12/2014 - 01:51
Drupal 8 Migrations, part 4: Migrating Nodes from Drupal 7 December 10th, 2014 Keith Dechant

Drupal 8 provides a flexible, plugin-based architecture for migrating data into a site. In Part 3 of this series, we explored how to migrate taxonomies from a Drupal 7 site. We will now expand on this by migrating basic nodes from a Drupal 7 site into Drupal 8.

The code examples in this post build on the migration module begun in Part 2 of this series. If you are trying this code out yourself, it is recommended to start building your custom migration module according to the examples in that post.

The game plan for migrating nodes

Because Drupal nodes can be of many types and have many different user-defined fields, it is complicated to write a single migration script that can handle all fields for all node types. To keep things simple, we will only migrate the built-in "Article" content type, which has the same default fields in Drupal 7 and Drupal 8.

The eventual plan of the Migrate Drupal core module is to build a dynamic migration path that can migrate the fields for any content type. When this is completed, it will likely supersede some of the code shown in this article.

The migration definition

Starting with the "Migrate Custom" module we created in Part 2, we now add the following configuration file.

modules/migrate_custom/config/install/migrate.migration.custom_article.yml

id: custom_article source: plugin: custom_article destination: plugin: entity:node type: article bundle: article process: nid: nid vid: vid type: type langcode: plugin: static_map bypass: true source: language map: und: en title: title uid: uid status: status created: created changed: changed promote: promote sticky: sticky 'body/format': plugin: static_map bypass: true source: body_format map: 1: plain_text 2: restricted_html 3: full_html 4: full_html 'body/value': body_value 'body/summary': body_summary field_tags: tags field_image: images

Pay attention to the last two fields in the definition, "field_tags" and "field_image." These fields can be configured to accept multiple values. (In the case of "field_image" the out-of-the-box configuration allows only one value, but this is easy to change using the Admin UI.) We account for these in the migration by providing only a single property name here. In our source plugin below, we will set these properties to be arrays, thus allowing as many values as exist in our source data.

What's more, "field_image", like the body, is a compound field, in this case consisting of a file ID, ALT text, width, and height. We could specify those values in the definition, but that would limit us to importing only one image. Instead, we will use an associative array in our source plugin to populate all the components of the compound field.

The source plugin

Similar to our Users source plugin in Part 2 of this series, our Blog source definition needs to implement both the query() and processRow() methods. We will do this in the following file:

modules/migrate_custom/src/Plugin/migrate/source/Article.php

<?php   /** * @file * Contains \Drupal\migrate_custom\Plugin\migrate\source\Article. */   namespace Drupal\migrate_custom\Plugin\migrate\source;   use Drupal\migrate\Plugin\SourceEntityInterface; use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;   /** * Drupal 7 Blog node source plugin * * @MigrateSource( * id = "custom_article" * ) */ class Article extends DrupalSqlBase implements SourceEntityInterface {   /** * {@inheritdoc} */ public function query() { // this queries the built-in metadata, but not the body, tags, or images. $query = $this->select('node', 'n') ->condition('n.type', 'article') ->fields('n', array( 'nid', 'vid', 'type', 'language', 'title', 'uid', 'status', 'created', 'changed', 'promote', 'sticky', )); $query->orderBy('nid'); return $query; }   /** * {@inheritdoc} */ public function fields() { $fields = $this->baseFields(); $fields['body/format'] = $this->t('Format of body'); $fields['body/value'] = $this->t('Full text of body'); $fields['body/summary'] = $this->t('Summary of body'); return $fields; }   /** * {@inheritdoc} */ public function prepareRow(Row $row) { $nid = $row->getSourceProperty('nid');   // body (compound field with value, summary, and format) $result = $this->getDatabase()->query(' SELECT fld.body_value, fld.body_summary, fld.body_format FROM {field_data_body} fld WHERE fld.entity_id = :nid ', array(':nid' => $nid)); foreach ($result as $record) { $row->setSourceProperty('body_value', $record->body_value ); $row->setSourceProperty('body_summary', $record->body_summary ); $row->setSourceProperty('body_format', $record->body_format ); }   // taxonomy term IDs // (here we use MySQL's GROUP_CONCAT() function to merge all values into one row.) $result = $this->getDatabase()->query(' SELECT GROUP_CONCAT(fld.field_tags_tid) as tids FROM {field_data_field_tags} fld WHERE fld.entity_id = :nid ', array(':nid' => $nid)); foreach ($result as $record) { if (!is_null($record->tids)) { $row->setSourceProperty('tags', explode(',', $record->tids) ); } }   // images $result = $this->getDatabase()->query(' SELECT fld.field_image_fid, fld.field_image_alt, fld.field_image_title, fld.field_image_width, fld.field_image_height FROM {field_data_field_image} fld WHERE fld.entity_id = :nid ', array(':nid' => $nid)); // Create an associative array for each row in the result. The keys // here match the last part of the column name in the field table. $images = []; foreach ($result as $record) { $images[] = [ 'target_id' => $record->field_files_fid, 'alt' => $record->field_image_alt, 'title' => $record->field_image_title, 'width' => $record->field_image_width, 'height' => $record->field_image_height, ]; } $row->setSourceProperty('images', $images);   return parent::prepareRow($row); }   /** * {@inheritdoc} */ public function getIds() { $ids['nid']['type'] = 'integer'; $ids['nid']['alias'] = 'n'; return $ids; }   /** * {@inheritdoc} */ public function bundleMigrationRequired() { return FALSE; }   /** * {@inheritdoc} */ public function entityTypeId() { return 'node'; }   /** * Returns the user base fields to be migrated. * * @return array * Associative array having field name as key and description as value. */ protected function baseFields() { $fields = array( 'nid' => $this->t('Node ID'), 'vid' => $this->t('Version ID'), 'type' => $this->t('Type'), 'title' => $this->t('Title'), 'format' => $this->t('Format'), 'teaser' => $this->t('Teaser'), 'uid' => $this->t('Authored by (uid)'), 'created' => $this->t('Created timestamp'), 'changed' => $this->t('Modified timestamp'), 'status' => $this->t('Published'), 'promote' => $this->t('Promoted to front page'), 'sticky' => $this->t('Sticky at top of lists'), 'language' => $this->t('Language (fr, en, ...)'), ); return $fields; }   } Running the migration

We need to add a new line to the end of our manifest.yml file:

- custom_article

Remember to reinstall the module to load the new migration configuration. See Part 3 of this series for more information.

As we did with our user migration, we now run the migration using Drush.

drush migrate-manifest manifest.yml --legacy-db-url=mysql://{dbuser}:{dbpass}@localhost/{dbname}

Next steps

This migration only handles a single content type, and only the fields that are configured in an out-of-the-box Drupal site. To practice what you have learned here, try adding some custom fields to the content types, then add them to the migration definition and source plugin. For more practice, try writing a custom migration for a different content type, like the Basic Page, or a custom content type from your site.

Categories: Elsewhere

Metal Toad: Drupal 8 Migrations, part 3: Migrating Taxonomies from Drupal 7

Thu, 11/12/2014 - 01:38
Drupal 8 Migrations, part 3: Migrating Taxonomies from Drupal 7 December 10th, 2014 Keith Dechant

Drupal 8 provides a flexible, plugin-based architecture for migrating data into a site. In Part 2 of this series, we explored how to migrate users from a Drupal 7 site. We will now expand on this by migrating Taxonomy vocabularies and terms from a Drupal 7 site into Drupal 8.

This article continues our work from Part 2. The code examples pick up where that post left off. If you are trying this code out yourself, it is recommended to start building your custom migration module according to the examples in that post.

Migrating Taxonomy Vocabularies

The Migrate Drupal module (in Drupal 8 core) already contains a migration definition and source plugins to migrate taxonomy data from Drupal 6 to Drupal 8. All we need to do is to adapt the existing code to work with Drupal 7.

The migration definition:

Starting with the "Migrate Custom" module we created in Part 2, we now add the following configuration file.

modules/migrate_custom/config/install/migrate.migration.custom_taxonomy_vocabulary.yml

id: custom_taxonomy_vocabulary label: Drupal 7 taxonomy vocabularies migration_groups: - Drupal 7 source: plugin: custom_taxonomy_vocabulary process: vid: - plugin: machine_name source: machine_name - plugin: dedupe_entity entity_type: taxonomy_vocabulary field: vid length: 32 label: name name: name description: description hierarchy: hierarchy module: module weight: weight destination: plugin: entity:taxonomy_vocabulary

Here we have examples of a few plugins not seen in the previous post:

  • machine_name converts the string into a valid machine name.
  • dedupe_entity prevents machine name conflicts, which would cause imported data to overwrite existing data. For example, a machine name "foo" would be renamed to "foo_2" if name "foo" already existed.

The source plugin

To define the source of our vocabulary data, we create a new file modules/migrate_custom/src/Plugin/migrate/source/Vocabulary.php with the following contents:

<?php   /** * @file * Contains \Drupal\migrate_custom\Plugin\migrate\source\Vocabulary. */   namespace Drupal\migrate_custom\Plugin\migrate\source;   use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;   /** * Drupal 7 vocabularies source from database. * * @MigrateSource( * id = "custom_taxonomy_vocabulary", * source_provider = "taxonomy" * ) */ class Vocabulary extends DrupalSqlBase {   /** * {@inheritdoc} */ public function query() { $query = $this->select('taxonomy_vocabulary', 'v') ->fields('v', array( 'vid', 'name', 'description', 'hierarchy', 'module', 'weight', 'machine_name' )); return $query; }   /** * {@inheritdoc} */ public function fields() { return array( 'vid' => $this->t('The vocabulary ID.'), 'name' => $this->t('The name of the vocabulary.'), 'description' => $this->t('The description of the vocabulary.'), 'help' => $this->t('Help text to display for the vocabulary.'), 'relations' => $this->t('Whether or not related terms are enabled within the vocabulary. (0 = disabled, 1 = enabled)'), 'hierarchy' => $this->t('The type of hierarchy allowed within the vocabulary. (0 = disabled, 1 = single, 2 = multiple)'), 'weight' => $this->t('The weight of the vocabulary in relation to other vocabularies.'), 'parents' => $this->t("The Drupal term IDs of the term's parents."), 'node_types' => $this->t('The names of the node types the vocabulary may be used with.'), ); }   /** * {@inheritdoc} */ public function getIds() { $ids['vid']['type'] = 'integer'; return $ids; }   }

Note: this file was adapted from the Drupal 6 version in Drupal 8 core. For the original file, see core/modules/migrate_drupal/src/Plugin/migrate/source/d6/Vocabulary.php

The structure of this file is similar to the User source plugin in the previous article. However, because all the data we need is stored in the `taxonomy_vocabulary` table in the source database, we do not need to define the prepareRow() method.

Migrating Taxonomy Terms

We can use a second migration definition to migrate our taxonomy terms. Create the following file:

modules/migrate_custom/config/install/migrate.migration.custom_taxonomy_term.yml

id: custom_taxonomy_term label: Drupal 7 taxonomy terms migration_groups: - Drupal 7 source: plugin: custom_taxonomy_term process: tid: tid vid: plugin: migration migration: custom_taxonomy_vocabulary source: vid name: name description: description weight: weight parent: - plugin: skip_process_on_empty source: parent - plugin: migration migration: custom_taxonomy_term changed: timestamp destination: plugin: entity:taxonomy_term migration_dependencies: required: - custom_taxonomy_vocabulary

In this migration, we make use of the migration process plugin for two of our properties, the vocabulary ID and the parent term ID. This preserves these references in case the referenced entity's ID or machine name changed during the import.

Some machine names and/or IDs will likely change when running your import. This is to be expected, especially because Drupal 8 stores taxonomy vocabularies in the 'config' table, where they are accessed by their machine names instead of by the numeric IDs used in Drupal 7. Fortunately for us, the Migrate module records a map of the old and new IDs in the database. We can then use the migration source plugin to easily look up the old ID or machine name.

The source plugin

To define the source of our term data, we create a new file modules/migrate_custom/src/Plugin/migrate/source/Term.php with the following contents:

<?php   /** * @file * Contains \Drupal\migrate_custom\Plugin\migrate\source\Term. */   namespace Drupal\migrate_custom\Plugin\migrate\source;   use Drupal\migrate\Row; use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;   /** * Drupal 7 taxonomy terms source from database. * * @todo Support term_relation, term_synonym table if possible. * * @MigrateSource( * id = "custom_taxonomy_term", * source_provider = "taxonomy" * ) */ class Term extends DrupalSqlBase {   /** * {@inheritdoc} */ public function query() { $query = $this->select('taxonomy_term_data', 'td') ->fields('td', array('tid', 'vid', 'name', 'description', 'weight', 'format')) ->distinct(); return $query; }   /** * {@inheritdoc} */ public function fields() { return array( 'tid' => $this->t('The term ID.'), 'vid' => $this->t('Existing term VID'), 'name' => $this->t('The name of the term.'), 'description' => $this->t('The term description.'), 'weight' => $this->t('Weight'), 'parent' => $this->t("The Drupal term IDs of the term's parents."), ); }   /** * {@inheritdoc} */ public function prepareRow(Row $row) { // Find parents for this row. $parents = $this->select('taxonomy_term_hierarchy', 'th') ->fields('th', array('parent', 'tid')) ->condition('tid', $row->getSourceProperty('tid')) ->execute() ->fetchCol(); $row->setSourceProperty('parent', $parents); return parent::prepareRow($row); }   /** * {@inheritdoc} */ public function getIds() { $ids['tid']['type'] = 'integer'; return $ids; }   } Reloading the configuration

Remember that migrations are configuration entities. To reload the configuration, we need to uninstall and reinstall our module. Here's a handy Drush command to do this:

drush pm-uninstall migrate_custom -y && drush en migrate_custom

Running the migration

We need to add some new lines to our manifest.yml file:

# A D7 user and taxonomy migration, with dependencies. - custom_user - custom_taxonomy_vocabulary - custom_taxonomy_term

As we did with our user migration, we now run the migration using Drush.

drush migrate-manifest manifest.yml --legacy-db-url=mysql://{dbuser}:{dbpass}@localhost/{dbname}

When including multiple migrations in a single manifest, be aware that drush migrate-manifest doesn't always run them in the order you specified. If, for example, your taxonomy migrations are being run before your user migration, and your taxonomy terms end up missing their UIDs, you might need to create two separate manifest files, to give yourself better control over the order.

Next post: Migrating Nodes from Drupal 7.

Categories: Elsewhere

Pages