summaryrefslogtreecommitdiff
path: root/sites/all/modules/media/js
diff options
context:
space:
mode:
authorCtibor Brančík <ctibor@brancik.cz>2016-03-20 19:27:01 +0100
committerCtibor Brančík <ctibor@brancik.cz>2016-03-20 19:27:01 +0100
commit29a6913890a675ddf1a9239b4407f105e02dc95d (patch)
treedd9ba21b73e9e704952b49d5153616a9dfa9b98f /sites/all/modules/media/js
parent5ddacae6306ce071d4f7e4d438960d6d3a4c6bd8 (diff)
downloadbrdo-29a6913890a675ddf1a9239b4407f105e02dc95d.tar.gz
brdo-29a6913890a675ddf1a9239b4407f105e02dc95d.tar.bz2
Added drupal modules for site
Diffstat (limited to 'sites/all/modules/media/js')
-rw-r--r--sites/all/modules/media/js/media.admin.js77
-rw-r--r--sites/all/modules/media/js/media.browser.js124
-rw-r--r--sites/all/modules/media/js/media.core.js19
-rw-r--r--sites/all/modules/media/js/media.js125
-rw-r--r--sites/all/modules/media/js/media.popups.js384
-rw-r--r--sites/all/modules/media/js/plugins/media.views.js173
-rw-r--r--sites/all/modules/media/js/util/ba-debug.min.js12
-rw-r--r--sites/all/modules/media/js/util/json2.js481
8 files changed, 1395 insertions, 0 deletions
diff --git a/sites/all/modules/media/js/media.admin.js b/sites/all/modules/media/js/media.admin.js
new file mode 100644
index 000000000..058e9318c
--- /dev/null
+++ b/sites/all/modules/media/js/media.admin.js
@@ -0,0 +1,77 @@
+/**
+ * @file
+ * Javascript for the interface at admin/content/media and also for interfaces
+ * related to setting up media fields and for media type administration.
+ *
+ * Basically, if it's on the /admin path, it's probably here.
+ */
+
+(function ($) {
+
+/**
+ * Functionality for the administrative file listings.
+ */
+Drupal.behaviors.mediaAdmin = {
+ attach: function (context) {
+ // Show a javascript confirmation dialog if a user has files selected and
+ // they try to switch between the "Thumbnail" and "List" local tasks.
+ $('.tabs.secondary a').once('media-admin').bind('click', function () {
+ if ($(':checkbox:checked', $('.file-entity-admin-file-form')).length != 0) {
+ return confirm(Drupal.t('If you switch views, you will lose your selection.'));
+ }
+ });
+
+ if ($('.media-display-thumbnails').length && !$('.media-thumbnails-select').length) {
+ // Implements 'select all/none' for thumbnail view.
+ // @TODO: Support grabbing more than one page of thumbnails.
+ var allLink = $('<a href="#">' + Drupal.t('all') + '</a>')
+ .click(function () {
+ $('.media-display-thumbnails', $(this).parents('form')).find(':checkbox').attr('checked', true).change();
+ return false;
+ });
+ var noneLink = $('<a href="#">' + Drupal.t('none') + '</a>')
+ .click(function () {
+ $('.media-display-thumbnails', $(this).parents('form')).find(':checkbox').attr('checked', false).change();
+ return false;
+ });
+ $('<div class="media-thumbnails-select" />')
+ .append('<strong>' + Drupal.t('Select') + ':</strong> ')
+ .append(allLink)
+ .append(', ')
+ .append(noneLink)
+ .prependTo('.media-display-thumbnails')
+ // If the media item is clicked anywhere other than on the image itself
+ // check the checkbox. For the record, JS thinks this is wonky.
+ $('.media-item').bind('click', function (e) {
+ if ($(e.target).is('img, a')) {
+ return;
+ }
+ var checkbox = $(this).parent().find(':checkbox');
+ if (checkbox.is(':checked')) {
+ checkbox.attr('checked', false).change();
+ } else {
+ checkbox.attr('checked', true).change();
+ }
+ });
+
+ // Add an extra class to selected thumbnails.
+ $('.media-display-thumbnails :checkbox').each(function () {
+ var checkbox = $(this);
+ if (checkbox.is(':checked')) {
+ $(checkbox.parents('li').find('.media-item')).addClass('selected');
+ }
+
+ checkbox.bind('change.media', function () {
+ if (checkbox.is(':checked')) {
+ $(checkbox.parents('li').find('.media-item')).addClass('selected');
+ }
+ else {
+ $(checkbox.parents('li').find('.media-item')).removeClass('selected');
+ }
+ });
+ });
+ }
+ }
+};
+
+})(jQuery);
diff --git a/sites/all/modules/media/js/media.browser.js b/sites/all/modules/media/js/media.browser.js
new file mode 100644
index 000000000..88490e598
--- /dev/null
+++ b/sites/all/modules/media/js/media.browser.js
@@ -0,0 +1,124 @@
+/**
+ * @file
+ * Provides default functions for the media browser
+ */
+
+(function ($) {
+namespace('Drupal.media.browser');
+
+Drupal.media.browser.selectedMedia = [];
+Drupal.media.browser.mediaAdded = function () {};
+Drupal.media.browser.selectionFinalized = function (selectedMedia) {
+ // This is intended to be overridden if a callee wants to be triggered
+ // when the media selection is finalized from inside the browser.
+ // This is used for the file upload form for instance.
+};
+
+Drupal.behaviors.MediaBrowser = {
+ attach: function (context) {
+ if (Drupal.settings.media && Drupal.settings.media.selectedMedia) {
+ Drupal.media.browser.selectMedia(Drupal.settings.media.selectedMedia);
+ // Fire a confirmation of some sort.
+ Drupal.media.browser.finalizeSelection();
+ }
+
+ // Instantiate the tabs.
+ var showFunc = function(event, ui) {
+ // Store index of the tab being activated.
+ if (parent_iframe = Drupal.media.browser.getParentIframe(window)) {
+ $(parent_iframe).attr('current_tab', $('#media-tabs-wrapper > ul > li.ui-state-active').index());
+ }
+ };
+ var activeTab = Drupal.media.browser.tabFromHash();
+ $('#media-browser-tabset').once('MediaBrowser').tabs({
+ selected: activeTab, // jquery < 1.9
+ active: activeTab, // jquery >= 1.9
+ show: showFunc, // jquery ui < 1.8
+ activate: showFunc // jquery ui >= 1.8
+ });
+
+ $('.media-browser-tab').each( Drupal.media.browser.validateButtons );
+ }
+ // Wait for additional params to be passed in.
+};
+
+Drupal.media.browser.getParentIframe = function (window) {
+ var arrFrames = parent.document.getElementsByTagName("IFRAME");
+ for (var i = 0; i < arrFrames.length; i++) {
+ if (arrFrames[i].contentWindow === window) {
+ return arrFrames[i];
+ }
+ }
+}
+
+/**
+ * Get index of the active tab from window.location.hash
+ */
+Drupal.media.browser.tabFromHash = function () {
+ if (parent_iframe = Drupal.media.browser.getParentIframe(window)) {
+ return $(parent_iframe).attr('current_tab');
+ }
+ return 0;
+};
+
+Drupal.media.browser.launch = function () {
+
+};
+
+Drupal.media.browser.validateButtons = function() {
+ // The media browser runs in an IFRAME. The Drupal.media.popups.mediaBrowser()
+ // function sets up the IFRAME and an "OK" button that is outside of the
+ // IFRAME, so that its click handlers can destroy the IFRAME while retaining
+ // information about what media items were selected. However, Drupal UI
+ // convention is to place all action buttons on the same "line" at the bottom
+ // of the form, so if the form within the IFRAME contains a "Submit" button or
+ // other action buttons, then the "OK" button will appear below the IFRAME
+ // which breaks this convention and is confusing to the user. Therefore, we
+ // add a "Submit" button inside the IFRAME, and have its click action trigger
+ // the click action of the corresponding "OK" button that is outside the
+ // IFRAME. media.css contains CSS rules that hide the outside buttons.
+
+ // If a submit button is present, another round-trip to the server is needed
+ // before the user's selection is finalized. For these cases, when the form's
+ // real Submit button is clicked, the server either returns another form for
+ // the user to fill out, or else a completion page that contains or sets the
+ // Drupal.media.browser.selectedMedia variable. If the latter, then
+ // Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad() auto-triggers the
+ // "OK" button action to finalize the selection and remove the IFRAME.
+
+ // We need to check for the fake submit button that is used on non-form based
+ // pane content. On these items we need to bind the clicks so that media can
+ // be selected or the window can be closed. This is still a hacky approach,
+ // but it is a step in the right direction.
+
+ $('a.button.fake-submit', this).once().bind('click', Drupal.media.browser.submit);
+};
+
+Drupal.media.browser.submit = function () {
+ // @see Drupal.media.browser.validateButtons().
+ var buttons = $(parent.window.document.body).find('#mediaBrowser').parent('.ui-dialog').find('.ui-dialog-buttonpane button');
+ buttons[0].click();
+
+ // Return false to prevent the fake link "click" from continuing.
+ return false;
+}
+
+Drupal.media.browser.selectMedia = function (selectedMedia) {
+ Drupal.media.browser.selectedMedia = selectedMedia;
+};
+
+Drupal.media.browser.selectMediaAndSubmit = function (selectedMedia) {
+ Drupal.media.browser.selectedMedia = selectedMedia;
+ Drupal.media.browser.submit();
+};
+
+Drupal.media.browser.finalizeSelection = function () {
+ if (!Drupal.media.browser.selectedMedia) {
+ throw new exception(Drupal.t('Cannot continue, nothing selected'));
+ }
+ else {
+ Drupal.media.browser.selectionFinalized(Drupal.media.browser.selectedMedia);
+ }
+};
+
+}(jQuery));
diff --git a/sites/all/modules/media/js/media.core.js b/sites/all/modules/media/js/media.core.js
new file mode 100644
index 000000000..f99971c85
--- /dev/null
+++ b/sites/all/modules/media/js/media.core.js
@@ -0,0 +1,19 @@
+
+/**
+ * Creates a namespace.
+ *
+ * @return
+ * The created namespace object.
+ */
+function namespace () {
+ var a=arguments, o=null, i, j, d;
+ for (i=0; i<a.length; i=i+1) {
+ d=a[i].split(".");
+ o=window;
+ for (j=0; j<d.length; j=j+1) {
+ o[d[j]]=o[d[j]] || {};
+ o=o[d[j]];
+ }
+ }
+ return o;
+};
diff --git a/sites/all/modules/media/js/media.js b/sites/all/modules/media/js/media.js
new file mode 100644
index 000000000..5288dd307
--- /dev/null
+++ b/sites/all/modules/media/js/media.js
@@ -0,0 +1,125 @@
+/**
+ * @file
+ * Provides JavaScript additions to the media field widget.
+ *
+ * This file provides support for launching the media browser to select existing
+ * files and disabling of other media fields during Ajax uploads (which prevents
+ * separate media fields from accidentally attaching files).
+ */
+
+(function ($) {
+
+/**
+ * Attach behaviors to media element browse fields.
+ */
+Drupal.behaviors.mediaElement = {
+ attach: function (context, settings) {
+ if (settings.media && settings.media.elements) {
+ $.each(settings.media.elements, function(selector) {
+ $(selector, context).once('media-browser-launch', function () {
+ var configuration = settings.media.elements[selector];
+ // The user has JavaScript enabled, so display the browse field and hide
+ // the upload and attach fields which are only used as a fallback in
+ // case the user is unable to use the media browser.
+ $(selector, context).children('.browse').show();
+ $(selector, context).children('.upload').hide();
+ $(selector, context).children('.attach').hide();
+ $(selector, context).children('.browse').bind('click', {configuration: configuration}, Drupal.media.openBrowser);
+ });
+ });
+ }
+ }
+};
+
+/**
+ * Attach behaviors to the media attach and remove buttons.
+ */
+Drupal.behaviors.mediaButtons = {
+ attach: function (context) {
+ $('input.form-submit', context).bind('mousedown', Drupal.media.disableFields);
+ },
+ detach: function (context) {
+ $('input.form-submit', context).unbind('mousedown', Drupal.media.disableFields);
+ }
+};
+
+/**
+ * Media attach utility functions.
+ */
+Drupal.media = Drupal.media || {};
+
+/**
+ * Opens the media browser with the element's configuration settings.
+ */
+Drupal.media.openBrowser = function (event) {
+ var clickedButton = this;
+ var configuration = event.data.configuration.global;
+
+ // Find the file ID, preview and upload fields.
+ var fidField = $(this).siblings('.fid');
+ var previewField = $(this).siblings('.preview');
+ var uploadField = $(this).siblings('.upload');
+
+ // Find the edit and remove buttons.
+ var editButton = $(this).siblings('.edit');
+ var removeButton = $(this).siblings('.remove');
+
+ // Launch the media browser.
+ Drupal.media.popups.mediaBrowser(function (mediaFiles) {
+ // Ensure that there was at least one media file selected.
+ if (mediaFiles.length < 0) {
+ return;
+ }
+
+ // Grab the first of the selected media files.
+ var mediaFile = mediaFiles[0];
+
+ // Set the value of the hidden file ID field and trigger a change.
+ uploadField.val(mediaFile.fid);
+ uploadField.trigger('change');
+
+ // Find the attach button and automatically trigger it.
+ var attachButton = uploadField.siblings('.attach');
+ attachButton.trigger('mousedown');
+
+ // Display a preview of the file using the selected media file's display.
+ previewField.html(mediaFile.preview);
+ }, configuration);
+
+ return false;
+};
+
+/**
+ * Prevent media browsing when using buttons not intended to browse.
+ */
+Drupal.media.disableFields = function (event) {
+ var clickedButton = this;
+
+ // Only disable browse fields for Ajax buttons.
+ if (!$(clickedButton).hasClass('ajax-processed')) {
+ return;
+ }
+
+ // Check if we're working with an "Attach" button.
+ var $enabledFields = [];
+ if ($(this).closest('div.media-widget').length > 0) {
+ $enabledFields = $(this).closest('div.media-widget').find('input.attach');
+ }
+
+ // Temporarily disable attach fields other than the one we're currently
+ // working with. Filter out fields that are already disabled so that they
+ // do not get enabled when we re-enable these fields at the end of behavior
+ // processing. Re-enable in a setTimeout set to a relatively short amount
+ // of time (1 second). All the other mousedown handlers (like Drupal's Ajax
+ // behaviors) are excuted before any timeout functions are called, so we
+ // don't have to worry about the fields being re-enabled too soon.
+ // @todo If the previous sentence is true, why not set the timeout to 0?
+ var $fieldsToTemporarilyDisable = $('div.media-widget input.attach').not($enabledFields).not(':disabled');
+ $fieldsToTemporarilyDisable.attr('disabled', 'disabled');
+ setTimeout(function (){
+ $fieldsToTemporarilyDisable.attr('disabled', false);
+ }, 1000);
+};
+
+})(jQuery);
+
diff --git a/sites/all/modules/media/js/media.popups.js b/sites/all/modules/media/js/media.popups.js
new file mode 100644
index 000000000..2abb96e6f
--- /dev/null
+++ b/sites/all/modules/media/js/media.popups.js
@@ -0,0 +1,384 @@
+
+/**
+ * @file: Popup dialog interfaces for the media project.
+ *
+ * Drupal.media.popups.mediaBrowser
+ * Launches the media browser which allows users to pick a piece of media.
+ *
+ * Drupal.media.popups.mediaStyleSelector
+ * Launches the style selection form where the user can choose
+ * what format / style they want their media in.
+ *
+ */
+
+(function ($) {
+namespace('Drupal.media.popups');
+
+/**
+ * Media browser popup. Creates a media browser dialog.
+ *
+ * @param {function}
+ * onSelect Callback for when dialog is closed, received (Array
+ * media, Object extra);
+ * @param {Object}
+ * globalOptions Global options that will get passed upon initialization of the browser.
+ * @see Drupal.media.popups.mediaBrowser.getDefaults();
+ *
+ * @param {Object}
+ * pluginOptions Options for specific plugins. These are passed
+ * to the plugin upon initialization. If a function is passed here as
+ * a callback, it is obviously not passed, but is accessible to the plugin
+ * in Drupal.settings.variables.
+ *
+ * Example
+ * pluginOptions = {library: {url_include_patterns:'/foo/bar'}};
+ *
+ * @param {Object}
+ * widgetOptions Options controlling the appearance and behavior of the
+ * modal dialog.
+ * @see Drupal.media.popups.mediaBrowser.getDefaults();
+ */
+Drupal.media.popups.mediaBrowser = function (onSelect, globalOptions, pluginOptions, widgetOptions) {
+ var options = Drupal.media.popups.mediaBrowser.getDefaults();
+ options.global = $.extend({}, options.global, globalOptions);
+ options.plugins = pluginOptions;
+ options.widget = $.extend({}, options.widget, widgetOptions);
+
+ // Create it as a modal window.
+ var browserSrc = options.widget.src;
+ if ($.isArray(browserSrc) && browserSrc.length) {
+ browserSrc = browserSrc[browserSrc.length - 1];
+ }
+ // Params to send along to the iframe. WIP.
+ var params = {};
+ $.extend(params, options.global);
+ params.plugins = options.plugins;
+
+ browserSrc += '&' + $.param(params);
+ var mediaIframe = Drupal.media.popups.getPopupIframe(browserSrc, 'mediaBrowser');
+ // Attach the onLoad event
+ mediaIframe.bind('load', options, options.widget.onLoad);
+
+ /**
+ * Setting up the modal dialog
+ */
+ var ok = 'OK';
+ var notSelected = 'You have not selected anything!';
+
+ if (Drupal && Drupal.t) {
+ ok = Drupal.t(ok);
+ notSelected = Drupal.t(notSelected);
+ }
+
+ // @todo: let some options come through here. Currently can't be changed.
+ var dialogOptions = options.dialog;
+
+ dialogOptions.buttons[ok] = function () {
+ var selected = this.contentWindow.Drupal.media.browser.selectedMedia;
+ if (selected.length < 1) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(selected);
+ $(this).dialog("close");
+ };
+
+ var dialog = mediaIframe.dialog(dialogOptions);
+
+ Drupal.media.popups.sizeDialog(dialog);
+ Drupal.media.popups.resizeDialog(dialog);
+ Drupal.media.popups.scrollDialog(dialog);
+ Drupal.media.popups.overlayDisplace(dialog.parents(".ui-dialog"));
+
+ return mediaIframe;
+};
+
+Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad = function (e) {
+ var options = e.data;
+ if (this.contentWindow.Drupal.media == undefined) return;
+
+ if (this.contentWindow.Drupal.media.browser.selectedMedia.length > 0) {
+ var ok = (Drupal && Drupal.t) ? Drupal.t('OK') : 'OK';
+ var ok_func = $(this).dialog('option', 'buttons')[ok];
+ ok_func.call(this);
+ return;
+ }
+};
+
+Drupal.media.popups.mediaBrowser.getDefaults = function () {
+ return {
+ global: {
+ types: [], // Types to allow, defaults to all.
+ activePlugins: [] // If provided, a list of plugins which should be enabled.
+ },
+ widget: { // Settings for the actual iFrame which is launched.
+ src: Drupal.settings.media.browserUrl, // Src of the media browser (if you want to totally override it)
+ onLoad: Drupal.media.popups.mediaBrowser.mediaBrowserOnLoad // Onload function when iFrame loads.
+ },
+ dialog: Drupal.media.popups.getDialogOptions()
+ };
+};
+
+Drupal.media.popups.mediaBrowser.finalizeSelection = function () {
+ var selected = this.contentWindow.Drupal.media.browser.selectedMedia;
+ if (selected.length < 1) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(selected);
+ $(this).dialog("close");
+}
+
+/**
+ * Style chooser Popup. Creates a dialog for a user to choose a media style.
+ *
+ * @param mediaFile
+ * The mediaFile you are requesting this formatting form for.
+ * @todo: should this be fid? That's actually all we need now.
+ *
+ * @param Function
+ * onSubmit Function to be called when the user chooses a media
+ * style. Takes one parameter (Object formattedMedia).
+ *
+ * @param Object
+ * options Options for the mediaStyleChooser dialog.
+ */
+Drupal.media.popups.mediaStyleSelector = function (mediaFile, onSelect, options) {
+ var defaults = Drupal.media.popups.mediaStyleSelector.getDefaults();
+ // @todo: remove this awful hack :(
+ if (typeof defaults.src === 'string' ) {
+ defaults.src = defaults.src.replace('-media_id-', mediaFile.fid) + '&fields=' + encodeURIComponent(JSON.stringify(mediaFile.fields));
+ }
+ else {
+ var src = defaults.src.shift();
+ defaults.src.unshift(src);
+ defaults.src = src.replace('-media_id-', mediaFile.fid) + '&fields=' + encodeURIComponent(JSON.stringify(mediaFile.fields));
+ }
+ options = $.extend({}, defaults, options);
+ // Create it as a modal window.
+ var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaStyleSelector');
+ // Attach the onLoad event
+ mediaIframe.bind('load', options, options.onLoad);
+
+ /**
+ * Set up the button text
+ */
+ var ok = 'OK';
+ var notSelected = 'Very sorry, there was an unknown error embedding media.';
+
+ if (Drupal && Drupal.t) {
+ ok = Drupal.t(ok);
+ notSelected = Drupal.t(notSelected);
+ }
+
+ // @todo: let some options come through here. Currently can't be changed.
+ var dialogOptions = Drupal.media.popups.getDialogOptions();
+
+ dialogOptions.buttons[ok] = function () {
+
+ var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia();
+ if (!formattedMedia) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(formattedMedia);
+ $(this).dialog("close");
+ };
+
+ var dialog = mediaIframe.dialog(dialogOptions);
+
+ Drupal.media.popups.sizeDialog(dialog);
+ Drupal.media.popups.resizeDialog(dialog);
+ Drupal.media.popups.scrollDialog(dialog);
+ Drupal.media.popups.overlayDisplace(dialog.parents(".ui-dialog"));
+
+ return mediaIframe;
+};
+
+Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad = function (e) {
+};
+
+Drupal.media.popups.mediaStyleSelector.getDefaults = function () {
+ return {
+ src: Drupal.settings.media.styleSelectorUrl,
+ onLoad: Drupal.media.popups.mediaStyleSelector.mediaBrowserOnLoad
+ };
+};
+
+
+/**
+ * Style chooser Popup. Creates a dialog for a user to choose a media style.
+ *
+ * @param mediaFile
+ * The mediaFile you are requesting this formatting form for.
+ * @todo: should this be fid? That's actually all we need now.
+ *
+ * @param Function
+ * onSubmit Function to be called when the user chooses a media
+ * style. Takes one parameter (Object formattedMedia).
+ *
+ * @param Object
+ * options Options for the mediaStyleChooser dialog.
+ */
+Drupal.media.popups.mediaFieldEditor = function (fid, onSelect, options) {
+ var defaults = Drupal.media.popups.mediaFieldEditor.getDefaults();
+ // @todo: remove this awful hack :(
+ defaults.src = defaults.src.replace('-media_id-', fid);
+ options = $.extend({}, defaults, options);
+ // Create it as a modal window.
+ var mediaIframe = Drupal.media.popups.getPopupIframe(options.src, 'mediaFieldEditor');
+ // Attach the onLoad event
+ // @TODO - This event is firing too early in IE on Windows 7,
+ // - so the height being calculated is too short for the content.
+ mediaIframe.bind('load', options, options.onLoad);
+
+ /**
+ * Set up the button text
+ */
+ var ok = 'OK';
+ var notSelected = 'Very sorry, there was an unknown error embedding media.';
+
+ if (Drupal && Drupal.t) {
+ ok = Drupal.t(ok);
+ notSelected = Drupal.t(notSelected);
+ }
+
+ // @todo: let some options come through here. Currently can't be changed.
+ var dialogOptions = Drupal.media.popups.getDialogOptions();
+
+ dialogOptions.buttons[ok] = function () {
+ var formattedMedia = this.contentWindow.Drupal.media.formatForm.getFormattedMedia();
+ if (!formattedMedia) {
+ alert(notSelected);
+ return;
+ }
+ onSelect(formattedMedia);
+ $(this).dialog("close");
+ };
+
+ var dialog = mediaIframe.dialog(dialogOptions);
+
+ Drupal.media.popups.sizeDialog(dialog);
+ Drupal.media.popups.resizeDialog(dialog);
+ Drupal.media.popups.scrollDialog(dialog);
+ Drupal.media.popups.overlayDisplace(dialog);
+
+ return mediaIframe;
+};
+
+Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad = function (e) {
+
+};
+
+Drupal.media.popups.mediaFieldEditor.getDefaults = function () {
+ return {
+ // @todo: do this for real
+ src: '/media/-media_id-/edit?render=media-popup',
+ onLoad: Drupal.media.popups.mediaFieldEditor.mediaBrowserOnLoad
+ };
+};
+
+
+/**
+ * Generic functions to both the media-browser and style selector
+ */
+
+/**
+ * Returns the commonly used options for the dialog.
+ */
+Drupal.media.popups.getDialogOptions = function () {
+ return {
+ buttons: {},
+ dialogClass: Drupal.settings.media.dialogOptions.dialogclass,
+ modal: Drupal.settings.media.dialogOptions.modal,
+ draggable: Drupal.settings.media.dialogOptions.draggable,
+ resizable: Drupal.settings.media.dialogOptions.resizable,
+ minWidth: Drupal.settings.media.dialogOptions.minwidth,
+ width: Drupal.settings.media.dialogOptions.width,
+ height: Drupal.settings.media.dialogOptions.height,
+ position: Drupal.settings.media.dialogOptions.position,
+ overlay: {
+ backgroundColor: Drupal.settings.media.dialogOptions.overlay.backgroundcolor,
+ opacity: Drupal.settings.media.dialogOptions.overlay.opacity
+ },
+ zIndex: Drupal.settings.media.dialogOptions.zindex,
+ close: function (event, ui) {
+ $(event.target).remove();
+ }
+ };
+};
+
+/**
+ * Get an iframe to serve as the dialog's contents. Common to both plugins.
+ */
+Drupal.media.popups.getPopupIframe = function (src, id, options) {
+ var defaults = {width: '100%', scrolling: 'auto'};
+ var options = $.extend({}, defaults, options);
+
+ return $('<iframe class="media-modal-frame"/>')
+ .attr('src', src)
+ .attr('width', options.width)
+ .attr('id', id)
+ .attr('scrolling', options.scrolling);
+};
+
+Drupal.media.popups.overlayDisplace = function (dialog) {
+ if (parent.window.Drupal.overlay && jQuery.isFunction(parent.window.Drupal.overlay.getDisplacement)) {
+ var overlayDisplace = parent.window.Drupal.overlay.getDisplacement('top');
+ if (dialog.offset().top < overlayDisplace) {
+ dialog.css('top', overlayDisplace);
+ }
+ }
+}
+
+/**
+ * Size the dialog when it is first loaded and keep it centered when scrolling.
+ *
+ * @param jQuery dialogElement
+ * The element which has .dialog() attached to it.
+ */
+Drupal.media.popups.sizeDialog = function (dialogElement) {
+ if (!dialogElement.is(':visible')) {
+ return;
+ }
+ var windowWidth = $(window).width();
+ var dialogWidth = windowWidth * 0.8;
+ var windowHeight = $(window).height();
+ var dialogHeight = windowHeight * 0.8;
+
+ dialogElement.dialog("option", "width", dialogWidth);
+ dialogElement.dialog("option", "height", dialogHeight);
+ dialogElement.dialog("option", "position", 'center');
+
+ $('.media-modal-frame').width('100%');
+}
+
+/**
+ * Resize the dialog when the window changes.
+ *
+ * @param jQuery dialogElement
+ * The element which has .dialog() attached to it.
+ */
+Drupal.media.popups.resizeDialog = function (dialogElement) {
+ $(window).resize(function() {
+ Drupal.media.popups.sizeDialog(dialogElement);
+ });
+}
+
+/**
+ * Keeps the dialog centered when the window is scrolled.
+ *
+ * @param jQuery dialogElement
+ * The element which has .dialog() attached to it.
+ */
+Drupal.media.popups.scrollDialog = function (dialogElement) {
+ // Keep the dialog window centered when scrolling.
+ $(window).scroll(function() {
+ if (!dialogElement.is(':visible')) {
+ return;
+ }
+ dialogElement.dialog("option", "position", 'center');
+ });
+}
+
+})(jQuery);
diff --git a/sites/all/modules/media/js/plugins/media.views.js b/sites/all/modules/media/js/plugins/media.views.js
new file mode 100644
index 000000000..455288d65
--- /dev/null
+++ b/sites/all/modules/media/js/plugins/media.views.js
@@ -0,0 +1,173 @@
+/**
+ * @file
+ * Handles the JS for the views file browser.
+ *
+ * Note that this does not currently support multiple file selection
+ */
+
+(function ($) {
+
+namespace('Drupal.media.browser.views');
+Drupal.behaviors.mediaViews = {
+ attach: function (context, settings) {
+
+ // Make sure when pressing enter on text inputs, the form isn't submitted
+ $('.ctools-auto-submit-full-form .views-exposed-form input:text, input:text.ctools-auto-submit', context)
+ .filter(':not(.ctools-auto-submit-exclude)')
+ .bind('keydown keyup', function (e) {
+ if(e.keyCode === 13) {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ });
+ // Disable the links on media items list
+ $('.view-content ul.media-list-thumbnails a').click(function() {
+ return false;
+ });
+
+ // We loop through the views listed in Drupal.settings.media.browser.views
+ // and set them up individually.
+ var views_ids = [];
+ for(var key in Drupal.settings.media.browser.views){
+ views_ids.push(key);
+ }
+
+ for (var i = 0; i < views_ids.length; i++) {
+ var views_id = views_ids[i];
+ for (var j= 0; j < Drupal.settings.media.browser.views[views_id].length; j++) {
+ var views_display_id = Drupal.settings.media.browser.views[views_id][j],
+ view = $('.view-id-' + views_id + '.view-display-id-' + views_display_id);
+ if (view.length) {
+ Drupal.media.browser.views.setup(view);
+ }
+ }
+ }
+
+ // Reset the state on tab-changes- bind on the 'select' event on the tabset
+ $('#media-browser-tabset').bind('tabsselect', function(event, ui) {
+ var view = $('.view', ui.panel);
+ if (view.length) {
+ Drupal.media.browser.views.select(view);
+ }
+ });
+
+ }
+}
+
+/**
+ * Event-function that is called with a view, when the tab containing that
+ * view is selected.
+ */
+Drupal.media.browser.views.select = function(view) {
+ // Reset the list of selected files
+ Drupal.media.browser.selectMedia([]);
+
+ // Reset all 'selected'-status.
+ $('.view-content .media-item', view).removeClass('selected');
+}
+
+/**
+ * Setup function. Called once for every Media Browser view.
+ *
+ * Sets up event-handlers for selecting items in the view.
+ */
+Drupal.media.browser.views.setup = function(view) {
+ // Ensure we only setup each view once..
+ if ($(view).hasClass('media-browser-views-processed')) {
+ return;
+ }
+
+ // Reset the list of selected files
+ Drupal.media.browser.selectMedia([]);
+
+ // Catch double click to submit a single item.
+ $('.view-content .media-item', view).bind('dblclick', function () {
+ var fid = $(this).closest('.media-item[data-fid]').data('fid'),
+ selectedFiles = new Array();
+
+ // Remove all currently selected files
+ $('.view-content .media-item', view).removeClass('selected');
+
+ // Mark it as selected
+ $(this).addClass('selected');
+
+ // Because the files are added using drupal_add_js() and due to the fact
+ // that drupal_get_js() runs a drupal_array_merge_deep() which re-numbers
+ // numeric key values, we have to search in Drupal.settings.media.files
+ // for the matching file ID rather than referencing it directly.
+ for (index in Drupal.settings.media.files) {
+ if (Drupal.settings.media.files[index].fid == fid) {
+ selectedFiles.push(Drupal.settings.media.files[index]);
+
+ // If multiple tabs contains the same file, it will be present in the
+ // files-array multiple times, so we break out early so we don't have
+ // it in the selectedFiles array multiple times.
+ // This would interfer with multi-selection, so...
+ break;
+ }
+ }
+ Drupal.media.browser.selectMediaAndSubmit(selectedFiles);
+ });
+
+
+ // Catch the click on a media item
+ $('.view-content .media-item', view).bind('click', function () {
+ var fid = $(this).closest('.media-item[data-fid]').data('fid'),
+ selectedFiles = new Array();
+
+ // Remove all currently selected files
+ $('.view-content .media-item', view).removeClass('selected');
+
+ // Mark it as selected
+ $(this).addClass('selected');
+
+ // Multiselect!
+ if (Drupal.settings.media.browser.params.multiselect) {
+ // Loop through the already selected files
+ for (index in Drupal.media.browser.selectedMedia) {
+ var currentFid = Drupal.media.browser.selectedMedia[index].fid;
+
+ // If the current file exists in the list of already selected
+ // files, we deselect instead of selecting
+ if (currentFid == fid) {
+ $(this).removeClass('selected');
+ // If we change the fid, the later matching won't
+ // add it back again because it can't find it.
+ fid = NaN;
+
+ // The previously selected file wasn't clicked, so we retain it
+ // as an active file
+ }
+ else {
+ // Add to list of already selected files
+ selectedFiles.push(Drupal.media.browser.selectedMedia[index]);
+
+ // Mark it as selected
+ $('.view-content *[data-fid=' + currentFid + '].media-item', view).addClass('selected');
+ }
+ }
+ }
+
+ // Because the files are added using drupal_add_js() and due to the fact
+ // that drupal_get_js() runs a drupal_array_merge_deep() which re-numbers
+ // numeric key values, we have to search in Drupal.settings.media.files
+ // for the matching file ID rather than referencing it directly.
+ for (index in Drupal.settings.media.files) {
+ if (Drupal.settings.media.files[index].fid == fid) {
+ selectedFiles.push(Drupal.settings.media.files[index]);
+
+ // If multiple tabs contains the same file, it will be present in the
+ // files-array multiple times, so we break out early so we don't have
+ // it in the selectedFiles array multiple times.
+ // This would interfer with multi-selection, so...
+ break;
+ }
+ }
+ Drupal.media.browser.selectMedia(selectedFiles);
+ });
+
+ // Add the processed class, so we dont accidentally process the same element twice..
+ $(view).addClass('media-browser-views-processed');
+}
+
+}(jQuery));
diff --git a/sites/all/modules/media/js/util/ba-debug.min.js b/sites/all/modules/media/js/util/ba-debug.min.js
new file mode 100644
index 000000000..1c50e7fb8
--- /dev/null
+++ b/sites/all/modules/media/js/util/ba-debug.min.js
@@ -0,0 +1,12 @@
+/*
+ * debug - v0.3 - 6/8/2009
+ * http://benalman.com/projects/javascript-debug-console-log/
+ *
+ * Copyright (c) 2009 "Cowboy" Ben Alman
+ * Licensed under the MIT license
+ * http://benalman.com/about/license/
+ *
+ * With lots of help from Paul Irish!
+ * http://paulirish.com/
+ */
+window.debug=(function(){var c=this,e=Array.prototype.slice,b=c.console,i={},f,g,j=9,d=["error","warn","info","debug","log"],m="assert clear count dir dirxml group groupEnd profile profileEnd time timeEnd trace".split(" "),k=m.length,a=[];while(--k>=0){(function(n){i[n]=function(){j!==0&&b&&b[n]&&b[n].apply(b,arguments)}})(m[k])}k=d.length;while(--k>=0){(function(n,o){i[o]=function(){var q=e.call(arguments),p=[o].concat(q);a.push(p);h(p);if(!b||!l(n)){return}b.firebug?b[o].apply(c,q):b[o]?b[o](q):b.log(q)}})(k,d[k])}function h(n){if(f&&(g||!b||!b.log)){f.apply(c,n)}}i.setLevel=function(n){j=typeof n==="number"?n:9};function l(n){return j>0?j>n:d.length+j<=n}i.setCallback=function(){var o=e.call(arguments),n=a.length,p=n;f=o.shift()||null;g=typeof o[0]==="boolean"?o.shift():false;p-=typeof o[0]==="number"?o.shift():n;while(p<n){h(a[p++])}};return i})(); \ No newline at end of file
diff --git a/sites/all/modules/media/js/util/json2.js b/sites/all/modules/media/js/util/json2.js
new file mode 100644
index 000000000..39d8f3706
--- /dev/null
+++ b/sites/all/modules/media/js/util/json2.js
@@ -0,0 +1,481 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-09-29
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or '&nbsp;'),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the value
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (!this.JSON) {
+ this.JSON = {};
+}
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return isFinite(this.valueOf()) ?
+ this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());